Следует ли компилировать следующее по правилам С++ 11? Почему должно или почему нет? УБ есть? Кажется, что gcc запрещал это раньше, но передумал в версии 11. Microsoft принимает это, а clang постоянно нет.
Я был под выражением, что IntWrapper myInt = 42;
в этом случае просто синтаксический сахар и точно такой же, как IntWrapper myInt(42);
, и что перегрузка оператора присваивания не влияет на инициализацию. Мне кажется, что старые версии gcc и все версии clang хотят сделать ctor(int)
, а затем move
. В то время как msvc и новая версия gcc просто вызывают ctor(int)
, что я считаю правильным. Зачем делать ход, когда он не нужен.
Если бы какой-нибудь юрист языка С++ мог перевести это на английский, см. 11.10.1 Явная инициализация, страница 294-295 здесь https://isocpp.org/files/papers/N4860.pdf
Alternatively, a single assignment-expression can be specified as an initializer using the = form of initialization. Either direct-initialization semantics or copy-initialization semantics apply;
Note: Overloading of the assignment operator (12.6.2.1) has no effect on initialization. —
Как я понял, стандарт заключается в том, что компилятор может выбрать либо копирование, а затем перемещение, либо прямую инициализацию с использованием ctor, принимающего один аргумент. Что было бы странно, потому что как бы вы тогда узнали, компилируется он или нет.
#include <iostream>
struct IntWrapper
{
IntWrapper(int value) : m_value(value)
{
std::cout << "ctor(int)\n";
}
IntWrapper(IntWrapper&& that) = delete;
int m_value;
};
int main()
{
IntWrapper myInt = 42;
return 0;
}
компилятор | результат |
---|---|
msvc v.19.x | компилирует |
gcc 11.x | компилирует |
gcc 10.x | ошибка: использование удаленной функции 'IntWrapper::IntWrapper(IntWrapper&&) |
лязг 14.0.0 | ошибка: копирование переменной типа IntWrapper вызывает удаленный конструктор |
лязг 13.0.0 | ошибка: копирование переменной типа IntWrapper вызывает удаленный конструктор |
лязг 12.0.0 | ошибка: копирование переменной типа IntWrapper вызывает удаленный конструктор |
msvc v.19.x и gcc 11.x по умолчанию используют C++17 в качестве языкового стандарта для компиляции, в то время как все другие компиляторы, которые вы использовали, по умолчанию используют C++14. Вот почему вы видите разницу.
До C++17 IntWrapper myInt = 42;
семантически обрабатывается как IntWrapper myInt = IntWrapper(42);
, поэтому вам нужна неудаленная копия или конструктор перемещения, чтобы скомпилировать это, даже если временный объект исключен из-за оптимизации компилятора.
Поскольку C++17 IntWrapper myInt = 42;
теперь считается выполненным IntWrapper myInt{42};
и теперь мы напрямую конструируем, временный объект не создается. Эта функция называется гарантированное удаление копии.
Когда стандарт говорит
Either direct-initialization semantics or copy-initialization semantics apply;
Они означают, что IntWrapper myInt = 42;
можно рассматривать как IntWrapper myInt = IntWrapper(42);
или IntWrapper myInt{42};
. Это зависит от того, включена ли у вас оптимизация или нет, от того, какую форму вы получаете. Дополнительную часть стандарта, из-за которой это не работает до C++17, можно найти в [класс.временный]/1.
Even when the creation of the temporary object is unevaluated (Clause [expr]) or otherwise avoided ([class.copy]), all the semantic restrictions shall be respected as if the temporary object had been created and later destroyed. [ Note: This includes accessibility ([class.access]) and whether it is deleted, for the constructor selected and for the destructor. However, in the special case of a function call used as the operand of a decltype-specifier ([expr.call]), no temporary is introduced, so the foregoing does not apply to the prvalue of any such function call. — end note ]