struct X
{
X() { std::cout << \"default ctor\" << std::endl; }
};
int main()
{
X({});
}
This prints out
default c
To see what's really going on, declare copy and move constructors, compile in C++14 mode or earlier, and disable copy elision.
Coliru link
Output:
default ctor
move ctor
In the first snippet, the compiler looks for constructors of X
that take a single argument, since you've provided a single argument. These are the copy and move constructor, X::X(const X&)
and X::X(X&&)
, which the compiler will implicitly declare 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 elide the move, and in C++17 copy elision became mandatory.)
In the second snippet, the compiler now has a choice of converting {}
to X
(then calling the move constructor), or converting {}
to std::initializer_list
(then calling the initializer list constructor). According to [over.ics.list]/6.2, the conversion from {}
to X
, which calls the default constructor, is a user-defined conversion, while according to [over.ics.list]/4, the conversion from {}
to std::initializer_list
is the identity conversion. The identity conversion is better than a user-defined conversion, so the compiler calls the initializer list constructor.