Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ list initialization allows multiple user-defined conversions

I was reading this answer, which has the following example:

struct R {};
struct S { S(R); };
struct T { 
    T(const T &); //1 
    T(S);         //2
};
void f(T);
void g(R r) {
    f({r});
}

The answer is related to an old version of [over.best.ics]/4, which back then looked like this:

However, when considering the argument of a constructor or user-defined conversion function that is a candidate by [over.match.ctor] when invoked for the copying/moving of the temporary in the second step of a class copy-initialization, by [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X, or by [over.match.copy], [over.match.conv], or [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are considered.

In the answer it is said that without the highlighted part in the above quote, f({r}) would be ambiguous, because it could use either the first constructor of T (1) or the second constructor (2).

However, as much as I tried, I cannot see how the first constructor (1) is an option. f({r}) results in a copy-list-initialization of T from {r}. If the first constructor is used, the standard allows a conversion from r to the type of the parameter of the constructor. However, just one conversion is not enough, as one would have to go R --> S (using the converting constructor of S) and then S --> T (using the converting constructor of T (2)). And I cannot find anything in the standard that allows more than one user-defined conversion in the cast of list initialization.

I may be missing something. I would appreciate if someone pointed out where I am mistaken, or if I am not, I would like to know what is the purpose of the highlighted section in the quote from the standard.

The current version of the quoted paragraph requires that the only element of the initializer list be an initializer list itself, which would make sense in the example above, if instead of f({r}) there was f({{r}}). In that case, the explanation would be correct.

Thank you.

like image 245
user42768 Avatar asked Jul 31 '18 18:07

user42768


1 Answers

Your observation is correct. The example is well-formed even without the emphasized part, but the reason is a bit different.

And I cannot find anything in the standard that allows more than one user-defined conversion in the cast of list initialization.

In fact, [over.best.ics]/4 is exactly the rule that forbids more than one user-defined conversion. Consider (1) is called, then we must copy-initialize a temporary object of type T with r, which falls into the "or by [over.match.copy], [over.match.conv], or [over.match.ref] in all cases" part, thus user-defined conversions (r -> const T& and r -> S) are forbidden. As a result, we cannot form an implicit conversion sequence for (1), thus (2) wins.

Note the emphasized part was ever deleted due to issue 1758, and came back again with the constraint "the initializer list has exactly one element that is itself an initializer list" due to issue 2076.

like image 55
xskxzr Avatar answered Sep 24 '22 11:09

xskxzr