Should the following compile under c 11 rules? Why should it or why not? Is there UB? It seems that gcc forbade it before but changed their mind in version 11. Microsoft accepts it and clang consistently does not.
I was under the expression that IntWrapper myInt = 42;
in this case is just syntactic sugar and is exactly the same as IntWrapper myInt(42);
and that overloading of the assignment operator has no effect on initialization. To me it looks like old versions gcc and all versions of clang want to do ctor(int)
and then a move
. While msvc and new version gcc just call ctor(int)
which I think is correct. Why to do a move when it's not needed.
If some c language lawyer could translate this into english see 11.10.1 Explicit initialization page 294-295 here 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. —
How I understood the standard is that the compiler can choose either to do a copy then a move or directly initialize using the ctor taking one argument. Which would be weird because how would you then know does it compile or not.
#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;
}
compiler | result |
---|---|
msvc v.19.x | compiles |
gcc 11.x | compiles |
gcc 10.x | error: use of deleted function 'IntWrapper::IntWrapper(IntWrapper&&) |
clang 14.0.0 | error: copying variable of type 'IntWrapper' invokes deleted constructor |
clang 13.0.0 | error: copying variable of type 'IntWrapper' invokes deleted constructor |
clang 12.0.0 | error: copying variable of type 'IntWrapper' invokes deleted constructor |
CodePudding user response:
msvc v.19.x and gcc 11.x both default to using C 17 as the language standard to compile against while all of the other compilers you used default to C 14. This is why you see a difference.
Before C 17 IntWrapper myInt = 42;
is semantically treated as IntWrapper myInt = IntWrapper(42);
so you need a non-deleted copy or move constrcutor in order to compile that, even if the temporary object is elided away via compiler optimization.
Since C 17 IntWrapper myInt = 42;
is now treated as having done IntWrapper myInt{42};
and now we are directly constrcuting, no temporary object is created. This feature is called guaranteed copy elision
When the standard says
Either direct-initialization semantics or copy-initialization semantics apply;
They mean that IntWrapper myInt = 42;
can be treated as IntWrapper myInt = IntWrapper(42);
or IntWrapper myInt{42};
. It depends on if you have optimizations turned on or not on which form you get. The additional part of the standard that makes this fail before C 17 can be found in [class.temporary]/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 ]