Explicit constructors and nested initializer lists - c++

The following code successfully compiles with most modern C++11 compatible compilers (GCC >= 5.x, Clang, ICC, MSVC).
#include <string>
struct A
{
explicit A(const char *) {}
A(std::string) {}
};
struct B
{
B(A) {}
B(B &) = delete;
};
int main( void )
{
B b1({{{"test"}}});
}
But why does it compile in the first place, and how are the listed compilers interpreting that code?
Why is MSVC able to compile this without B(B &) = delete;, but the other 3 compilers all need it?
And why does it fail in all compilers except MSVC when I delete a different signature of the copy constructor, e.g. B(const B &) = delete;?
Are the compilers even all choosing the same constructors?
Why does Clang emit the following warning?
17 : <source>:17:16: warning: braces around scalar initializer [-Wbraced-scalar-init]
B b1({{{"test"}}});

Instead of explaining the behavior of compilers, I'll try to explain what the standard says.
Primary Example
To direct-initialize b1 from {{{"test"}}}, overload resolution applies to choose the best constructor of B. Because there is no implicit conversion from {{{"test"}}} to B& (list initializer is not a lvalue), the constructor B(B&) is not viable. We then focus on the constructor B(A), and check whether it is viable.
To determine the implicit conversion sequence from {{{"test"}}} to A (I will use the notation {{{"test"}}} -> A for simplicity), overload resolution applies to choose the best constructor of A, so we need to compare {{"test"}} -> const char* and {{"test"}} -> std::string (note the outermost layer of braces is elided) according to [over.match.list]/1:
When objects of non-aggregate class type T are list-initialized such that [dcl.init.list] specifies that overload resolution is performed according to the rules in this subclause, overload resolution selects the constructor in two phases:
Initially, the candidate functions are the initializer-list constructors ([dcl.init.list]) of the class T...
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.
... In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.
Note all constructors are considered here regardless of the specifier explicit.
{{"test"}} -> const char* does not exist according to [over.ics.list]/10 and [over.ics.list]/11:
Otherwise, if the parameter type is not a class:
if the initializer list has one element that is not itself an initializer list...
if the initializer list has no elements...
In all cases other than those enumerated above, no conversion is possible.
To determine {{"test"}} -> std::string, the same process is taken, and overload resolution chooses the constructor of std::string that takes a parameter of type const char*.
As a result, {{{"test"}}} -> A is done by choosing the constructor A(std::string).
Variations
What if explicit is removed?
The process does not change. GCC will choose the constructor A(const char*) while Clang will choose the constructor A(std::string). I think it is a bug for GCC.
What if there are only two layers of braces in the initializer of b1?
Note {{"test"}} -> const char* does not exist but {"test"} -> const char* exists. So if there are only two layers of braces in the initializer of b1, the constructor A(const char*) is chosen because {"test"} -> const char* is better than {"test"} -> std::string. As a result, an explicit constructor is chosen in copy-list-initialization (initialization of the parameter A in the constructor B(A) from {"test"}), then the program is ill-formed.
What if the constructor B(const B&) is declared?
Note this also happens if the declaration of B(B&) is removed. This time we need to compare {{{"test"}}} -> A and {{{"test"}}} -> const B&, or {{{"test"}}} -> const B equivalently.
To determine {{{"test"}}} -> const B, the process described above is taken. We need to compare {{"test"}} -> A and {{"test"}} -> const B&. Note {{"test"}} -> const B& does not exist according to [over.best.ics]/4:
However, if the target is
— the first parameter of a constructor or
— the implicit object parameter of a user-defined conversion function
and the constructor or user-defined conversion function is a candidate by
— [over.match.ctor], when the argument is the temporary in the second step of a class copy-initialization,
— [over.match.copy], [over.match.conv], or [over.match.ref] (in all cases), or
— the second phase of [over.match.list] when the initializer list has exactly one element that is itself an initializer list, and the target is the first parameter of a constructor of class X, and the conversion is to X or
reference to cv X,
user-defined conversion sequences are not considered.
To determine {{"test"}} -> A, the process described above is taken again. This is almost the same as the case we talked in the previous subsection. As a result, the constructor A(const char*) is chosen. Note the constructor is chosen here to determine {{{"test"}}} -> const B, and does not apply actually. This is permitted though the constructor is explicit.
As a result, {{{"test"}}} -> const B is done by choosing the constructor B(A), then the constructor A(const char*). Now both {{{"test"}}} -> A and {{{"test"}}} -> const B are user-defined conversion sequences and neither is better than the other, so the initialization of b1 is ambiguous.
What if the parentheses is replaced by braces?
According to [over.best.ics]/4, which is block-quoted in the previous subsection, the user defined conversion {{{"test"}}} -> const B& is not considered. So the result is the same as the primary example even if the constructor B(const B&) is declared.

B b1({{{"test"}}}); is like B b1(A{std::string{const char*[1]{"test"}}});
16.3.3.1.5 List-initialization sequence [over.ics.list]
4 Otherwise, if the parameter type is a character array 133 and the initializer list has a single element that is an appropriately-typed string literal (11.6.2), the implicit conversion sequence is the identity conversion.
And the compiler tries all possible implicit conversions. For example if we have class C with the following constructors:
#include <string>
struct C
{
template<typename T, size_t N> C(const T* (&&) [N]) {}
template<typename T, size_t N> C(const T (&&) [N]) {}
template<typename T=char> C(const T* (&&)) {}
template<typename T=char> C(std::initializer_list<char>&&) {}
};
struct A
{
explicit A(const char *) {}
A(C ) {}
};
struct B
{
B(A) {}
B(B &) = delete;
};
int main( void )
{
const char* p{"test"};
const char p2[5]{"test"};
B b1({{{"test"}}});
}
The clang 5.0.0 compiler could not decide which to use and fails with:
29 : <source>:29:11: error: call to constructor of 'C' is ambiguous
B b1({{{"test"}}});
^~~~~~~~~~
5 : <source>:5:40: note: candidate constructor [with T = char, N = 1]
template<typename T, size_t N> C(const T* (&&) [N]) {}
^
6 : <source>:6:40: note: candidate constructor [with T = const char *, N = 1]
template<typename T, size_t N> C(const T (&&) [N]) {}
^
7 : <source>:7:39: note: candidate constructor [with T = char]
template<typename T=char> C(const T* (&&)) {}
^
15 : <source>:15:9: note: passing argument to parameter here
A(C ) {}
^
But if we leave only one of the non-initializer-list constructors the code compiles fine.
GCC 7.2 just picks the C(const T* (&&)) {} and compiles. If it's not available it takes C(const T* (&&) [N]).
MSVC just fails with:
29 : <source>(29): error C2664: 'B::B(B &)': cannot convert argument 1 from 'initializer list' to 'A'

(Edited, thanks #dyp)
Here's a partial answer and speculative, explaining how I've gone about interpreting what happens, not being a compilation expert and not much of a C++ Guru.
First I'll go with some intuition and common sense. Obviously the last thing to happen is a B::B(A), since that's the only constructor available for B b1 (apparently it can't be a B::B(B&&) because there's at least one copy constructor defined, so B::B(B&&) is not implicitly defined for us). Also, the first construction of an A or a B to happen can't be an A::A(const char*) because that one's explicit, so there must be some use of A::A(std::string). Also, the innermost quoted text is a const char[5]. So I'll guess the first, innermost, construction is a const char*; and then a string construction: std::string::string(const char *). There's one more curly-bracket construction, and I would guess it's A::A(A&&) (or maybe A::A(A&)?). So, to summarize my intuitive guess, the order of constructions should be:
A const char*
An std::string (which is really std::basic_string<whatever>)
an A
a B
Then I put this on GodBolt, with GCC as the first example. (Alternatively, you could just compile it yourself while keeping the assembly language output, and pass that through c++filt to make it more readable). Here are all the lines specifically mentioning C++ code:
call 4006a0 <std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)#plt>
call 400858 <A::A(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>
call 400868 <B::B(A)>
call 400680 <std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()#plt>
call 400690 <std::allocator<char>::~allocator()#plt>
call 400690 <std::allocator<char>::~allocator()#plt>
So it seems the order of proper actionable constructions we see is:
(not seeing 1.)
2. std::basic_string::basic_string(const char* /* ignoring the allocator */)
3. A::A(std::string)
4. B::B(A)
With clang 5.0.0, results are similar IIANM, and as for MSVC - who knows? Maybe it's a bug? They have been known to be a bit dodgy sometimes on properly supporting the language standard all the way. Sorry, like I said - partial answer.

Related

Overload resolution between two constructors from std::initializer_list

In following program, struct C has two constructors : one from std::initializer_list<A> and the other from std::initializer_list<B>. Then an object of the struct is created with C{{1}}:
#include <initializer_list>
struct A {
int i;
};
struct B {
constexpr explicit B(int) {}
};
struct C {
int v;
constexpr C(std::initializer_list<A>) : v(1) {}
constexpr C(std::initializer_list<B>) : v(2) {}
};
static_assert( C{{1}}.v == 1 );
Since only aggregate A can be implicitly constructed from int, one could expect that C(std::initializer_list<A>) is preferred and the program succeeds. And indeed it does in Clang.
However GCC complains:
error: call of overloaded 'C(<brace-enclosed initializer list>)' is ambiguous
note: candidate: 'constexpr C::C(std::initializer_list<B>)'
note: candidate: 'constexpr C::C(std::initializer_list<A>)'
and so does MSVC:
error C2440: '<function-style-cast>': cannot convert from 'initializer list' to 'C'
note: No constructor could take the source type, or constructor overload resolution was ambiguous
Demo: https://gcc.godbolt.org/z/joz91q4ed
Which compiler is correct here?
The wording could be clearer (which is unsurprising), but GCC and MSVC are correct here: the relevant rule ([over.ics.list]/7) checks only that
overload resolution […] chooses a single best constructor […] to perform the initialization of an object of type X from the argument initializer list
so the fact that the initialization of B from {1} would be ill-formed is irrelevant.
There are several such places where implicit conversion sequences are more liberal than actual initialization, causing certain cases to be ambiguous even though some of the possibilities wouldn’t actually work. If the programmer was confused and thought one of those near misses was actually a better match, it’s a feature that the ambiguity is reported.

Why is a templatized copy constructor not called? [duplicate]

This question already has answers here:
C++ template copy constructor on template class
(3 answers)
Closed 2 years ago.
Why is it that the following code fails to compile (complains about deleted copy constructor):
struct C {
int i;
C(const C&) = delete;
C(int i) : i(i) {}
template <typename T>
C(const T& value) {
cout << "in templatized constructor!\n";
this->i = value.i * 2;
}
};
int main()
{
C s1{4};
C s2{s1};
cout << s2.i;
}
But when i remove the const from the templatized copy constructor, everything works:
template <typename T>
C(T& value) {
cout << "in templatized constructor!\n";
this->i = value.i * 2;
}
Two questions:
(1) Why isn't the template contructor used as a fall-back when the generated copy constructor is deleted?
(2) Even though "templates can't be copy constructors" - how/why is it that the C(T&); constructor IS being used as a copy constructor (in the second example) ? surely this contradicts the idea that templates can't be copy constructors?
It's just overload resolution. In C s2{s1};, you are looking for a constructor of C that can take a (non-const) C lvalue argument. For template<typename T> C(T const&), the "closest" this constructor can get to matching the argument list is setting T = C, so you get C(C const&) (this solution for T is found by template argument deduction, which we don't need to go into). The copy constructor C(C const&) = delete; can also be called with this argument list. The two implicit conversion sequences involved (C lvalue undergoes identity conversion to bind to C const&), are also exactly the same, so neither overload is immediately "better" than the other than the other. However, the deleted one is not a template, so it's considered to be better anyway. The deleted overload is chosen, causing an error. If you have instead template<typename T> C(T&), then it can get closer to the given argument list, by setting T = C to get C(C&). Now, the implicit conversion sequence where a C lvalue binds to a C& (the case of the template) is better than the implicit conversion sequence where a C lvalue binds to a C const& (the copy constructor), since there are fewer qualifiers to add, so now the template wins and the code compiles.
Addressing the comments, your templated constructor indeed isn't a copy constructor. According to cppreference,
A copy constructor of class T is a non-template constructor...
Also, you may be misunderstanding what = delete; means. C(C const&) = delete; does not mean "there is not an overload with this signature", it means "there is an overload with this signature, and trying to call it is an error". You can't not have a copy constructor as one of the overloads of a class's constructor. deleteing it only makes it an error to use that overload but doesn't stop overload resolution from picking it over another overload.

Implicit conversion between two classes based on integral type

I have the situation where I have a class A, that provides a constructor for an integral type, and a class B that provides a implicit conversion operator for the same integral type. However, if I call a function accepting a reference to class A with an instance of class B, the compilation fails. I would have expected an implicit conversion of class B to the type accepted by the constructor of class A. Of course, if I add a constructor to A accepting class B, everything is fine. Is this behavior intended? Please checkout the example below.
#include <iostream>
class B
{
public:
B() = default;
B(const std::uint8_t &val) : mVal(val) {}
std::uint8_t get() const { return mVal; }
operator std::uint8_t() const { return mVal; }
private:
std::uint8_t mVal;
};
class A
{
public:
A() = default;
A(const std::uint8_t &val) : mVal(val) {}
// No problem if this exists
// A(const B &b) : mVal(b.get()) {}
std::uint8_t get() const { return mVal; }
private:
std::uint8_t mVal;
};
void func(const A &a)
{
std::cout << static_cast<int>(a.get()) << std::endl;
}
int main(int, char*[])
{
std::uint8_t val = 0xCE;
A a(val);
B b(val);
func(val); // fine
func(a); // fine
func(b); // error
}
There is a rule in C++ that no implicit conversion will use two user-defined conversions.
This is because such "long-distance" conversions can result in extremely surprising results.
If you want to be able to convert from anything that can convert to a uint8_t you can do:
template<class IntLike,
std::enable_if_t<std::is_convertible_v<IntLike, std::uint8_t>, bool> =true,
std::enable_if_t<!std::is_same_v<A, std::decay_t<IntLike>>, bool> =true
>
A( IntLike&& intlike ):A( static_cast<std::uint8_t>(std::forward<IntLike>(intlike)) )
{}
or you could cast your B to an uint8_t at the point you want to convert to an A.
You can do a similar thing in B where you create a magical template<class T, /*SFINAE magic*/> operator T that converts to anything that can be constructed by an uint8_t.
This obscure code:
std::enable_if_t<std::is_convertible_v<IntLike, std::uint8_t>, bool> =true,
std::enable_if_t<!std::is_same_v<A, std::decay_t<IntLike>>, bool> =true
exists to make sure that the overload is only used if the type we are converting from has the properties we want.
The first enable_if clause states that we only want things that can convert to uint8_t. The second states we don't want this constructor to be used for the type A itself, even if it passes the first.
Whenever you create a forwarding reference implicit constructor for a type, that second clause is pretty much needed or you get some other surprising issues.
The technique used is called SFINAE or Substitution Failure Is Not An Error. When a type IntType is deduced and those tests fail, there is substitution failure in those clauses. Usually this would cause an error, but when evaluating template overloads it is not an error because SFINAE; instead, it just blocks this template from being considered in overload resolution.
You are only allowed one user defined conversion when implicitly creating a object. Since func needs an A you would have a user defined conversion to turn B into a std::uint8_t and then another user defined conversion to turn that std::uint8_t into an A. What you would need is a operator A in B or a constructor in A that takes a B if you want it to happen implicitly. Otherwise you can just explicitly cast so you only need a single implicit one like
func(static_cast<std::uint8_t>(b)); // force it to a uint8_t
// or
func({b}); // make b the direct initializer for an A which will implicitly cast
// or
func(A{b}); same as #2 above but explicitly sating it
Is this behavior intended?
Yes, it is intended.
An implicit conversion sequence can have at most one user-defined conversion (constructor or conversion function).
Standard says (emphasis mine):
[over.ics.user]
A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user-
defined conversion (15.3) followed by a second standard conversion sequence. ...
In order for a user defined type (a class) to be implicitly convertible to another, there must be a constructor or conversion operator directly to that type. Implicit conversion (from user defined type to another) is not possible through an intermediate type.
You could use explicit conversion instead.

Curly braces constructor prefers initializer_list over better match. Why?

#include <vector>
using std::size_t;
struct Foo
{
Foo(size_t i, char c) {}
};
Foo Bar1()
{
size_t i = 0;
char c = 'x';
return { i, c }; // good
}
std::vector<char> Bar2()
{
size_t i = 0;
char c = 'x';
return { i, c }; // bad
}
https://wandbox.org/permlink/87uD1ikpMkThPTaw
warning: narrowing conversion of 'i' from 'std::size_t {aka long
unsigned int}' to 'char' inside { }
Obviously it tries to use the initializer_list of vector. But why doesn't it use the better match vector<char>(size_t, char) ?
Can i use the desired constructor in a return statement without writing the type again?
Because initializer_list constructors, if at all possible, take precedence over other constructors. This is to make edge cases less confusing - specifically, this particular vector constructor that you expect it to use was deemed too easily selected by accident.
Specifically, the standard says in 16.3.1.7 "Initialization by list-initialization" [over.match.list] (latest draft, N4687):
(1) When objects of non-aggregate class type T are list-initialized such that 11.6.4 specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:
Initially, the candidate functions are the initializer-list constructors (11.6.4) of the class T and the argument list consists of the initializer list as a single argument.
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.
So if you do std::vector<char>( i, c ), this section does not apply at all, since it isn't list-initialization. Normal overload resolution is applied, the (size_t, char) constructor is found, and used.
But if you do std::vector<char>{ i, c }, this is list-initialization. The initializer list constructors are tried first, and the (initializer_list<char>) constructor is a match (even though it involves the narrowing conversion from size_t to char), so it is used before the size+value constructor is ever considered.
So to answer the edited-in question: no, you can't create the vector without naming its type. But in C++17 you can use class template argument deduction and simply write return std::vector(i, c);

C++11 constructor overload resolution and initialiser_lists: clang++ and g++ disagree

I have a small piece of C++11 code which g++ (4.7 or 4.8) refuses to compile claiming that the call to constructor for B2 b2a(x, {P(y)}) is ambiguous. Clang++ is happy with that code, but refuses to compile B2 b2b(x, {{P(y)}}) which g++ is perfectly happy to compile!
Both compilers are perfectly happy with the B1 constructor with either {...} or {{...}} as an argument. Can any C++ language lawyer explain which compiler is correct (if either) and what is going on? Code below:
#include <initializer_list>
using namespace std;
class Y {};
class X;
template<class T> class P {
public:
P(T);
};
template<class T> class A {
public:
A(initializer_list<T>);
};
class B1 {
public:
B1(const X&, const Y &);
B1(const X&, const A<Y> &);
};
class B2 {
public:
B2(const X &, const P<Y> &);
B2(const X &, const A<P<Y>> &);
};
int f(const X &x, const Y y) {
B1 b1a(x, {y});
B1 b1b(x, {{y}});
B2 b2a(x, {P<Y>(y)});
B2 b2b(x, {{P<Y>(y)}});
return 0;
}
and the compiler errors, clang:
$ clang++ -stdlib=libc++ -std=c++11 test-initialiser-list-4.cc -o test.o -c
test-initialiser-list-4.cc:32:6: error: call to constructor of 'B2' is ambiguous
B2 b2(x, {{P<Y>(y)}});
^ ~~~~~~~~~~~~~~
test-initialiser-list-4.cc:26:5: note: candidate constructor
B2(const X &, const P<Y> &);
^
test-initialiser-list-4.cc:27:5: note: candidate constructor
B2(const X &, const A<P<Y>> &);
^
g++:
test-initialiser-list-4.cc: In function 'int f(const X&, Y)':
test-initialiser-list-4.cc:32:21: error: call of overloaded 'B2(const X&, <brace-enclosed initializer list>)' is ambiguous
B2 b2(x, {P<Y>(y)});
^
test-initialiser-list-4.cc:32:21: note: candidates are:
test-initialiser-list-4.cc:27:5: note: B2::B2(const X&, const A<P<Y> >&)
B2(const X &, const A<P<Y>> &);
^
test-initialiser-list-4.cc:26:5: note: B2::B2(const X&, const P<Y>&)
B2(const X &, const P<Y> &);
^
This smells like an interaction between uniform initialisation, initialiser list syntax and function overloading with templated arguments (which I know g++ is fairly stringent about), but I'm not enough of a standards lawyer to be able to unpack what should be the correct behaviour here!
First code, then what I think should happen. (In what follows, I will ignore the first parameter, since we are interested only into the second parameter. The first one is always an exact match in your example). Please note that the rules are currently in flux in the spec, so I wouldn't say that one or the other compiler has a bug.
B1 b1a(x, {y});
This code cannot call the const Y& constructor in C++11, because Y is an aggregate and Y has no data member of type Y (of course) or something else initializable by it (this is something ugly, and is worked on to be fixed - the C++14 CD doesn't have wording for this yet, so I am not sure whether final C++14 will contain this fix).
The constructor with the const A<Y>& parameter can be called - {y} will be taken as the argument to the constructor of A<Y>, and will initialize that constructor's std::initializer_list<Y>.
Hence - second constructor called successfully.
B1 b1b(x, {{y}});
Here, the basically same argument counts counts for the constructor with the const Y& parameter.
For the constructor with parameter type const A<Y>&, it is a bit more complicated. The rule for conversion cost in overload resolution computing the cost of initializing an std::initializer_list<T> requires every element of the braced list to be convertible to T. However we before said that {y} cannot be converted to Y (as it is an aggregate). It is now important to know whether std::initializer_list<T> is an aggregate or not. Frankly, I have no idea whether or not it must be considered to be an aggregate according to the Standard library clauses.
If we take it to be a non-aggregate, then we would be considering the copy constructor of std::initializer_list<Y>, which however again would trigger the exact same sequence of tests (leading to "infinite recursion" in overload resolution checking). Since this is rather weird and non-implementable, I don't think any implementation takes this path.
If we take std::initializer_list to be an aggregate, we will be saying "nope, no conversion found" (see the above aggregates-issue). In that case since we cannot call the initializer constructor with the single initializer list as a whole, {{y}} will be split up into multiple arguments, and the constructor(s) of A<Y> will be taking each of those separately. Hence, in this case, we would end up with {y} initializing a std::initializer_list<Y> as the single parameter - which is perfectly fine and work like a charm.
So under the assumption that std::initializer_list<T> is an aggregate, this is fine and call the second constructor successfully.
B2 b2a(x, {P<Y>(y)});
In this case and the next case, we don't have the aggregate issue like above with Y anymore, since P<Y> has a user-provided constructor.
For the P<Y> parameter constructor, that parameter will be initialized by {P<Y> object}. As P<Y> has no initializer lists, the list will be split up into individual arguments and call the move-constructor of P<Y> with an rvalue object of P<Y>.
For the A<P<Y>> parameter constructor, it is the same as the above case with A<Y> initialized by {y}: Since std::initializer_list<P<Y>> can be initialized by {P<Y> object}, the argument list is not split, and hence the braces are used to initializer that constructor'S std::initializer_list<T>.
Now, both constructors work fine. They are acting 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 in both cases the same conversion function or constructor is used - not the case here. Hence, this is ambiguous in C++11 (and in the C++14 CD).
Note that here we have a subtle point to explore
struct X { operator int(); X(){/*nonaggregate*/} };
void f(X);
void f(int);
int main() {
X x;
f({x}); // ambiguity!
f(x); // OK, calls first f
}
This counter intuitive result will probably be fixed in the same run with fixing the aggregate-initialization weirdness mentioned above (both will call the first f). This is implemented by saying that {x}->X becomes an identity conversion (as is X->x). Currently, it is a user-defined conversion.
So, ambiguity here.
B2 b2b(x, {{P<Y>(y)}});
For the constructor with parameter const P<Y>&, we again split the arguments and get {P<Y> object} argument passed to the constructor(s) of 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), because it would require a user defined conversion. The only constructor left is the one taking Y, but an Y cannot be initialized by {P<Y> object}.
For the constructor with parameter A<P<Y>>, the {{P<Y> object}} can initialize a std::initializer_list<P<Y>>, because {P<Y> object} is convertible to P<Y> (other than with Y above - dang, aggregates).
So, second constructor called successfully.
Summary for all 4
second constructor called successfully
under the assumption that std::initializer_list<T> is an aggregate, this is fine and call the second constructor successfully
ambiguity here
second constructor called successfully