Consider the following:
struct X {
X() {}
X(X&&) { puts("move"); }
};
X x = X();
In C++14, the move could be elided despite the fact that the move constructor has side effects thanks to [class.copy]/31,
This elision of copy/move operations ... is permitted in the following circumstances ... when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type
In C++17 this bullet was removed. Instead the move is guaranteed to be elided thanks to [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. [ Example:
T x = T(T(T()));
calls theT
default constructor to initializex
. — end example ]
Thus far the facts I've stated are well-known. But now let's change the code so that it reads:
X x({});
In C++14, overload resolution is performed and {}
is converted to a temporary of type X
using the default constructor, then moved into x
. The copy elision rules allow this move to be elided.
In C++17, the overload resolution is the same, but now [dcl.init]/17.6.1 doesn't apply and the bullet from C++14 isn't there anymore. There is no initializer expression, since the initializer is a braced-init-list. Instead it appears that [dcl.init]/(17.6.2) applies:
Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (16.3.1.3), and the best one is chosen through overload resolution (16.3). 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 appears to require the move constructor to be called, and if there's a rule elsewhere in the standard that says it's ok to elide it, I don't know where it is.
{}
is converted to a temporary of typeX
... which is then bound to the reference from the move constructor. I don't think your initial quote applies. – DoordieX
by{}
and ofX&&
by a temporary ofX
(or{}
, weasel-worded again). Arguably, nowhere is anX
initialized by a temporary there. – Beehive