Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What could go wrong if copy-list-initialization allowed explicit constructors?

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?

like image 324
R. Martinho Fernandes Avatar asked Feb 06 '12 07:02

R. Martinho Fernandes


2 Answers

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).

like image 181
Johannes Schaub - litb Avatar answered Sep 19 '22 12:09

Johannes Schaub - litb


Isn't it because 'explicit' is there to stop implicit casting, and you're asking it to do an implicit cast?

Would you be asking the question if you has specified the structure with a single argument constructor?

like image 33
Tom Tanner Avatar answered Sep 18 '22 12:09

Tom Tanner