The first code, what I think should happen. (In the future, I will ignore the first parameter, since we are only interested in the second parameter, the first of which is always an exact match in your example). Note that the rules currently apply in the specification, so I would not say that one or the other compiler has an error.
B1 b1a(x, {y});
This code cannot call the const Y& constructor in C ++ 11, because Y is an aggregate, and Y does not have a data item of type Y (of course) or anything else initialized by it (this is something ugly and it is fixed - there is no wording on the C ++ 14 CD yet, so I'm not sure if there will be this fix in the latest C ++ 14).
You can call the constructor with the parameter const A<Y>& - {y} will be used as an argument of the constructor A<Y> and initializes this constructor std::initializer_list<Y> .
Therefore, the second constructor was successfully called.
B1 b1b(x, {{y}});
Here basically the same argument counts the number of samples for the constructor with the const Y& parameter.
For a constructor with the parameter type const A<Y>& it is a bit more complicated. The rule for conversion cost when overload resolution calculates the initialization cost std::initializer_list<T> requires that each element of the parenthesis list be converted to T However, we said earlier that {y} cannot be converted to Y (since this is a collection). Now it is important to know whether std::initializer_list<T> collection or not. Honestly, I have no idea whether this should be considered an aggregate in accordance with standard library articles.
If we take it as a non-aggregate, then we will consider the copy constructor std::initializer_list<Y> , which, however, will repeat the same sequence of tests (which will lead to "infinite recursion" when checking overload resolution), since it is rather strange and unrealizable, I don't think any implementation takes this way.
If we take std::initializer_list as a collection, we will say βno, no conversion was foundβ (see the above problems with aggregates). In this case, since we cannot call the initializer constructor with a single list of initializers in general, {{y}} will be divided into several arguments, and the constructor A<Y> will take each of them separately. Therefore, in this case, we would end up with {y} initializing a std::initializer_list<Y> as the only parameter - which works fine and works like a charm.
So, assuming std::initializer_list<T> is an aggregate, this normally and successfully calls the second constructor.
B2 b2a(x, {P<Y>(y)});
In this case and in the following case, we no longer have such a common problem, as indicated above, with Y , since P<Y> has a user-provided constructor.
For the parameter constructor P<Y> this parameter will be initialized {P<Y> object} . Since P<Y> has no initialization lists, the list will be split into separate arguments and will call the constructor move P<Y> with the rvalue object P<Y> .
For parameter constructor A<P<Y>> it is similar to the above case with A<Y> initialized {y} : Since std::initializer_list<P<Y>> can be initialized {P<Y> object} , the argument list is not broken, and therefore, braces are used to initialize the constructor std::initializer_list<T> .
Now both constructors are working fine. They act like overloaded functions here, and their second parameter in both cases requires a user-defined conversion. User-defined conversion sequences can only be compared if the same conversion function or constructor is used in both cases - this is not the case here. Therefore, this is ambiguous in C ++ 11 (and on the C ++ 14 CD).
Please note that here we have a thin point to study
struct X { operator int(); X(){} }; void f(X); void f(int); int main() { X x; f({x});
This counter intuitive result is likely to be fixed in the same run with the above initialization oddities fixed (both will call the first f). This is realized by saying that {x}->X becomes an identity transformation (as is X->x ). This is currently a custom conversion.
So, the ambiguity is here.
B2 b2b(x, {{P<Y>(y)}});
For the constructor with the parameter const P<Y>& we again separate the arguments and get the argument {P<Y> object} passed to the constructor (s) P<Y> . Remember that P<Y> has a copy constructor. But the complication here is that we are not allowed to use it (see 13.3.3.1p4), since this will require a user-defined transformation. The only constructor on the left is the one that accepts Y , but Y cannot be initialized {P<Y> object} .
For a constructor with the parameter A<P<Y>> {{P<Y> object}} can initialize a std::initializer_list<P<Y>> , because {P<Y> object} can be converted to P<Y> (except for Y above - dang, aggregates).
So, the second constructor was successfully called.
Summary for all 4
- second constructor successfully called
- assuming
std::initializer_list<T> is an aggregate, this normally and successfully calls the second constructor - ambiguity here
- second constructor successfully called