The rules for list initialization of class types are basically: first, do overload resolution only considering std::initializer_list
constructors and then, if necessary, do overload resolution on all the constructors (this is [over.match.list]).
When initializing a std::initializer_list<E>
from an initializer list, it's as if we materialized a const E[N]
from the N elements in the initializer list (from [dcl.init.list]/5).
For vector<string> v = {{"a", "b"}};
we first try the initializer_list<string>
constructor, which would involve trying to initialize an array of 1 const string
, with the one string
initialized from {"a", "b"}
. This is viable because of the iterator-pair constructor of string
, so we end up with a vector containing one single string (which is UB because we violate the preconditions of that string constructor). This is the easy case.
For vector<int> v = {{2, 3}};
we first try the initializer_list<int>
constructor, which would involve trying to initialize an array of 1 const int
, with the one int
initialized from {2, 3}
. This is not viable.
So then, we redo overload resolution considering all the vector
constructors. Now, we get two viable constructors:
vector(vector&& )
, because when we recursively initialize the parameter there, the initializer list would be {2, 3}
- with which we would try to initialize an array of 2 const int
, which is viable.
vector(std::initializer_list<int> )
, again. This time not from the normal list-initialization world but just direct-initializing the initializer_list
from the same {2, 3}
initializer list, which is viable for the same reasons.
To pick which constructor, we have to go to into [over.ics.list], where the vector(vector&& )
constructor is a user-defined conversion sequence but the vector(initializer_list<int> )
constructor is identity, so it's preferred.
For completeness, vector(vector const&)
is also viable, but we'd prefer the move constructor to the copy constructor for other reasons.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…