In an answer to this question: Initializing vector<string> with double curly braces
it is shown that
vector<string> v = {{"a", "b"}};
will call the std::vector
constructor with an initializer_list
with one element. So the first (and only) element in the vector will be constructed from {"a", "b"}
. This leads to undefined behavior, but that is beyond the point here.
What I have found is that
std::vector<int> v = {{2, 3}};
Will call std::vector
constructor with an initializer_list
of two elements.
Why is the reason for this difference in behavior?
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 aconst E[N]
from the N elements in the initializer list (from [dcl.init.list]/5).For
vector<string> v = {{"a", "b"}};
we first try theinitializer_list<string>
constructor, which would involve trying to initialize an array of 1const string
, with the onestring
initialized from{"a", "b"}
. This is viable because of the iterator-pair constructor ofstring
, 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 theinitializer_list<int>
constructor, which would involve trying to initialize an array of 1const int
, with the oneint
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 2const int
, which is viable.vector(std::initializer_list<int> )
, again. This time not from the normal list-initialization world but just direct-initializing theinitializer_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 thevector(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.The difference in behavior is due to default parameters.
std::vector
has this c'tor:Note the second argument.
{2, 3}
is deduced asstd::initializer_list<int>
(no conversion of the initializers is required) and the allocator is defaulted.