Предположим, я написал:
Foo get_a_foo() {
return reinterpret_cast<Foo>(get_a_bar());
}
и предположим, что sizeof(Foo) == sizeof(Bar)
.
Это reinterpret_cast
недопустимо для всех возможных типов Foo
и Bar
. Это работает только в случаях, когда:
Bar
— это указатель, а Foo
— либо указатель, либо целое число/перечисление, достаточно большое для хранения указателей.
Bar
— целое число/перечисление, достаточно большое для хранения указателя, а Foo
— указатель.
Bar
— это тип объекта, а Foo
— это ссылочный тип.
Есть пара других случаев, которые я не освещал, но они либо не имеют отношения к делу (nullptr_t
кастинг), либо подпадают под аналогичные проблемы для № 1 или № 2.
Видите ли, элизия на самом деле не имеет значения при работе с фундаментальными типами. Вы не можете сказать разницу между исключением копирования/перемещения фундаментальных типов и его отсутствием. Так есть ли там конверсия? Компилятор просто использует регистр возвращаемого значения? Это зависит от компилятора с помощью правила «как если бы».
И elision не применяется при возврате ссылочных типов, поэтому № 3 отсутствует.
Но если Foo
и Bar
являются определяемыми пользователем типами объектов (или типами объектов, отличными от указателей, целых чисел или указателей-членов), приведение будет имеет неправильный формат. reinterpret_cast
не является какой-то тривиальной функцией преобразования memcpy
.
Итак, давайте заменим это некоторым кодом, который действительно может работать:
Foo get_a_foo()
{
return std::bit_cast<Foo>(get_a_bar());
}
Где C++20 std::bit_cast
эффективно преобразует один тривиальный копируемый тип в другой тривиальный копируемый тип.
Это преобразование все еще не было бы исключено. Или, по крайней мере, не в том смысле, в каком обычно используется слово «элизион».
Поскольку два типа легко копируются, а bit_cast
будет вызывать только тривиальные конструкторы, компилятор, безусловно, может стереть конструкторы и даже использовать объект возвращаемого значения get_a_foo
в качестве объекта возвращаемого значения get_a_bar
. Таким образом, это можно было бы считать «элизионом».
Но «выброс» обычно относится к той части стандарта, которая позволяет реализации игнорировать даже нетривиальные конструкторы/деструкторы. Компилятор может выполнять только описанное выше, потому что все конструкторы и деструкторы тривиальны. Если бы они были нетривиальными, их нельзя было бы игнорировать (опять же, если бы они были нетривиальными, std::bit_cast
не работало бы).
Я хочу сказать, что оптимизация приведенного выше преобразования не связана с правилами «элизион» или RVO; это полностью связано с правилом «как если бы». Даже в C++17 вопрос о том, будет ли вызов bit_cast
эффективным оператором noop, полностью зависит от компилятора. Да, после создания значения Foo
prvalue "выведение" его копии в объект возвращаемого значения функции требуется для С++ 17.
Но само обращение не является вопросом исключения.
person
Nicol Bolas
schedule
05.01.2018
Foo
не является указателем, ссылкой, целым числом или типом перечисления, результатом, скорее всего, будет ошибка компиляции. Если это один из них, то вам, вероятно, не нужен RVO, потому что возвращаемое значение тривиально копируемое и маленькое, и в большинстве архитектур оно будет помещено в регистр. - person Daniel H   schedule 05.01.2018