In the C++ standard, §13.3.1.7 [over.match.list], the following is stated:
In copy-list-initialization, if an
explicit
constructor is chosen, the initialization is ill-formed.
This is the reason why we can't do, for example, something like this:
struct foo {
// explicit because it can be called with one argument
explicit foo(std::string s, int x = 0);
private:
// ...
};
void f(foo x);
f({ "answer", 42 });
(Note that what happens here is not a conversion, and it would not be one even if the constructor was "implicit". This is initialization of a foo
object using its constructor directly. Other than the std::string
, there is no conversion here.)
This seems perfectly fine to me. There's no way that an implicit conversion will bite me.
If { "answer", 42 }
can initialize something else, the compiler won't betray me and do the wrong thing:
struct bar {
// explicit because it can be called with one argument
explicit bar(std::string s, int x = 0);
private:
// ...
};
void f(foo x);
void f(bar x);
f({ "answer", 42 }); // error: ambiguous call
There's no problem: the call is ambiguous, the code won't compile, and I'll have to pick the overload explicitly.
f(bar { "answer", 42 }); // ok
Since the prohibition is explicitly stated, I have the feeling that I am missing something here. As far as I can see, list initialization picking explicit constructors doesn't seem like a problem to me: by using list initialization syntax the programmer is already expressing the desire to do some kind of "conversion".
What could go wrong? What am I missing?
Conceptually copy-list-initialization is the conversion of a compound value to a destination type. The paper that proposed wording and explained rationale already considered the term "copy" in "copy list initialization" unfortunate, since it doesn't really convey the actual rationale behind it. But it is kept for compatibility with existing wording. A {10, 20}
pair/tuple value should not be able to copy initialize a String(int size, int reserve)
, because a string is not a pair.
Explicit constructors are considered but forbidden to be used. This makes sense in cases as follows
struct String {
explicit String(int size);
String(char const *value);
};
String s = { 0 };
0
does not convey the value of a string. So this results in an error because both constructors are considered, but an explicit
constructor is selected, instead of the 0
being treated as a null pointer constant.
Unfortunately this also happens in overload resolution across functions
void print(String s);
void print(std::vector<int> numbers);
int main() { print({10}); }
This is ill-formed too because of an ambiguity. Some people (including me) before C++11 was released thought that this is unfortunate, but didn't came up with a paper proposing a change regarding this (as far as I am aware).