Given a class, A
with a user-defined constructor:
struct A
{
A(int) {}
};
and another one, B
, accepting A
as a constructor parameter:
struct B
{
B(A) {}
};
then in order to perform the initialization as below:
B b({0});
the compiler has to consider the following candidates:
B(A); // #1
B(const B&); // #2
B(B&&); // #3
trying to find an implicit conversion sequence from {0}
to each of the parameters.
Note that B b({0})
does not list-initialize b
-- the (copy-)list-initialization applies to a constructor parameter itself.
Since the argument is an initializer list, the implicit conversion sequence needed to match the argument to a parameter is defined in terms of list-initialization sequence [over.ics.list]/p1:
When an argument is an initializer list ([dcl.init.list]), it is not an expression and special rules apply for converting it to a parameter type.
It reads:
[...], if the parameter is a non-aggregate class X and overload resolution per 13.3.1.7 chooses a single
best constructor of X to perform the initialization of an object of type X from the argument initializer list,
the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion
sequence an identity conversion. If multiple constructors are viable but none is better than the others, the
implicit conversion sequence is the ambiguous conversion sequence. User-defined conversions are allowed
for conversion of the initializer list elements to the constructor parameter types except as noted in 13.3.3.1.
For #1 to be viable, the following call must be valid:
A a = {0};
which is correct due to [over.match.list]/p1:
— If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T
and the argument list consists of the elements of the initializer list.
i.e., class A
has a constructor that accepts an int
argument.
For #2 to be a valid candidate, the following call must be valid:
const B& b = {0};
which according to [over.ics.ref]/p2:
When a parameter of reference type is not bound directly to an argument expression, the conversion sequence is the one required to convert the argument expression to the referenced type according to [over.best.ics]. Conceptually, this conversion sequence corresponds to copy-initializing a temporary of the referenced type with the argument expression. Any difference in top-level cv-qualification is subsumed by the initialization itself and does not constitute a conversion.
translates to:
B b = {0};
Once again, following [over.ics.list]/p6:
User-defined conversions are allowed for conversion of the initializer list elements to the constructor parameter types [...]
the compiler is allowed to use the user-defined conversion:
A(int);
to convert the argument 0
to B
's constructor parameter A
.
For candidate #3, the same reasoning applies as in #2. Eventually, the compiler cannot choose between the aforementioned implicit conversion sequences {citation needed}, and reports ambiguity.