Это ошибка?
Да.Это ошибка в MSVC. Практически любой компилятор, поддерживающий C ++ 17, компилирует его.Ниже мы имеем сборку, произведенную:
И все они компилируют его с -std=c++17
или -std=c++1z
(для ellcc).
Что говорится в стандарте?
Условные выражения (сформированные тернарным оператором) создают значения в соответствии с этими правилами (см. Раздел 8.5.16).
В параграфе 1 раздела 8.5.16 описывается последовательность, а в частях 2–7 описывается категория значений результирующего выражения (описание категорий значений см. В разделе 8.2.1).
- В пункте 2 рассматривается случай, когда второй или третий операнды являются недействительными.
- В пункте 3 рассматривается случай, когда и второй, и третий операнды являются битовыми полями с оценкой (т. Е. Не являются prvalues))
- Пункт 4 охватывает случай, когда второй и третий операнды имеют разные типы
- Пункт 5 охватывает случай, когда второй и третий операнды являются glvalues одного и того же типа (также не prvalues)
- Пункт 6:
В противном случае результатом является значение prvalue.Если второй и третий операнды не имеют один и тот же тип и оба имеют (возможно, cv-квалифицированный) тип класса, разрешение перегрузки используется для определения преобразований (если они есть), которые должны применяться к операндам (16.3.1.2, 16.6),Если не удается разрешить перегрузку, программа работает некорректно.В противном случае применяются определенные таким образом преобразования, а преобразованные операнды используются вместо исходных операндов в оставшейся части этого подпункта.
Это дает нам наш ответ.Результатом является значение prvalue, поэтому нет необходимости использовать конструкторы копирования или перемещения, так как значение будет создано в памяти, предоставленной вызывающей функцией (это местоположение в памяти передается как «скрытый» параметр вашей функции).
Ваша программа неявно ссылается на конструктор перемещения или конструктор копирования?
Джон Харпер был достаточно любезен, чтобы указать, что стандарт заявляет:
Программакоторая ссылается на удаленную функцию неявно или явно, кроме как для ее объявления, является неправильно сформированной.(11.4.3.2)
Возникает вопрос: неявно ли ваша программа ссылается на конструктор перемещения или конструктор копирования?
Ответ на этот вопрос - нет. Поскольку результатом условного выражения является значение prvalue, временные данные не материализуются, и в результате ни на конструктор перемещения, ни на конструктор копирования не ссылаются ни явно, ни неявно.Процитируем cppreference (выделение мое):
При следующих обстоятельствах компиляторы обязаны опустить копирование и переместить конструкцию объектов класса, даже если конструктор копирования / перемещенияи деструктор имеет наблюдаемые побочные эффекты.Объекты создаются непосредственно в хранилище, где они в противном случае были бы скопированы / перемещены. Конструкторы копирования / перемещения не обязательно должны присутствовать или быть доступными , поскольку языковые правила гарантируют, что операция копирования / перемещения не выполняется, даже концептуально:
В возвратеоператор, когда операнд является prvalue того же типа класса (игнорируя квалификацию cv), что и тип возврата функции:
T f() { return T(); }
f(); // only one call to default constructor of T
- При инициализации переменной, когда выражение инициализатора является prvalue того же типа класса (игнорируя квалификацию cv), что и тип переменной:
T x = T(T(f())); // only one call to default constructor of T, to initialize x
Различение между NRVO и RVO
Одним из источников разногласий является вопрос о том, гарантируется ли Copy Elision.Важно различать оптимизацию именованных возвращаемых значений и чистую оптимизацию возвращаемых значений.
Если вы возвращаете локальную переменную, это не гарантируется.Это именованная оптимизация возвращаемого значения.Если ваш оператор возврата является выражением, которое является prvalue, оно гарантировано.
Например:
NoCopyOrMove foo() {
NoCopyOrMove myVar{}; //Initialize
return myVar; //Error: Move constructor deleted
}
Я возвращаю выражение (myVar
), которое является именемобъект автоматического хранения.В этом случае оптимизация возвращаемого значения разрешена, но не гарантируется.Раздел 15.8.3 стандарта применяется здесь.
С другой стороны, если я напишу:
NoCopyOrMove foo() {
return NoCopyOrMove(); // No error (C++17 and above)
}
Копирование Elision гарантировано , и копирование или перемещение не выполняются.Точно так же, если я напишу:
NoCopyOrMove foo(); //declare foo
NoCopyOrMove bar() {
return foo(); //Returns what foo returns
}
Копировать Elision все еще гарантировано , потому что результат foo()
является prvalue.
Заключение
MSVC действительно имеет ошибку.