To find out what is really going on, declare the copies and move the constructors, compile them in C ++ 14 or earlier mode and disable copying.
Link Coliru
Output:
default ctor move ctor
In the first snippet, the compiler looks for X constructors that take one argument, since you provided one argument. This is a copy and move constructor, X::X(const X&) and X::X(X&&) , which the compiler will declare implicitly for you if you do not declare them yourself. The compiler then converts {} to an X object using the default constructor and passes that X object to the move constructor. (You must use fno-elide-constructors to see this, otherwise the compiler will return to the move, and in C ++ 17, copying has become mandatory.)
In the second fragment, the compiler now has a choice for converting {} to X (then calling the move constructor) or converting {} to std::initializer_list<int> (then calling the constructor of the initializer list). According to [over.ics.list] /6.2, the conversion from {} to X , which calls the default constructor, is a user-defined conversion, whereas according to [over.ics.list] / 4, the conversion from {} to std::initializer_list<int> is an identity transformation. An identity conversion is better than a custom conversion, so the compiler calls the constructor of the initializer list.
source share