Consider this example:
struct T { };
struct S {
operator T();
};
S s;
T t = s;
[dcl.init] will take us to [over.match.copy] which will find the conversion function operator T()
. But are we done at that point, or do we have to invoke T(T&& rhs)
, binding rhs
to the return of operator T()
via [dcl.init.ref]? Are there any differences with regards to the answer to this question between C++11 and C++1z?
If a copy constructor, copy-assignment operator, move constructor, move-assignment operator, or destructor is explicitly declared, then: No move constructor is automatically generated. No move-assignment operator is automatically generated.
A move constructor allows the resources owned by an rvalue object to be moved into an lvalue without creating its copy. An rvalue is an expression that does not have any memory address, and an lvalue is an expression with a memory address.
For non-union class types (class and struct), the move constructor performs full member-wise move of the object's bases and non-static members, in their initialization order, using direct initialization with an xvalue argument.
A move constructor is called: when an object initializer is std::move(something) when an object initializer is std::forward<T>(something) and T is not an lvalue reference type (useful in template programming for "perfect forwarding") when an object initializer is a temporary and the compiler doesn't eliminate the copy/move entirely.
- GeeksforGeeks What is conversion constructor in C++? In C++, if a class has a constructor which can be called with a single argument, then this constructor becomes conversion constructor because such a constructor allows automatic conversion to the class being constructed.
Trivial move constructor. A trivial move constructor is a constructor that performs the same action as the trivial copy constructor, that is, makes a copy of the object representation as if by std::memmove. All data types compatible with the C language (POD types) are trivially movable.
When a constructor takes only one argument then this type of constructors becomes conversion constructor. This type of constructor allows automatic conversion to the class being constructed.
This falls under [dcl.init]/17.6.3, which is pretty clear about what happens after overload resolution selects the conversion function:
The function selected is called with the initializer expression as its argument; if the function is a constructor, the call is a prvalue of the cv-unqualified version of the destination type whose result object is initialized by the constructor. The call is used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.
In your case this in turn recurses into [dcl.init]/17.6.1:
If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object.
In C++11 the second step does invoke a move constructor, since it doesn't have the bullet corresponding to C++17's 17.6.1. Instead you do the direct-initialization/overload resolution dance again:
If the initialization is direct-initialization, [...], constructors are considered. The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
This move can (and in practice will) be elided; see [class.copy]/31.
The more interesting case is actually
T t(s);
which under the C++17 wording is actually required to call a move constructor, because it uses the direct-initialization rule and does overload resolution on T
's constructors. That selects T
's move constructor and calls it to initialize t
, converting s
to a T
prvalue that is materialized into a temporary and bound to the parameter of the move constructor. The 17.6.1 bullet is simply not reachable in the process, and the bullet in C++11's [class.copy]/31 (now [class.copy.elision]/1) that permitted elision in this scenario was removed in C++17.
This is most likely a defect.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With