In the initialization of a vector of pairs
std::vector<std::pair<int, std::string>> foo{{1.0, "one"}, {2.0, "two"}};
how am I supposed to interpret the construction of foo? As I understand it,
- The constructor is called with braced initialization syntax so the overload
vector( std::initializer_list<T> init, const Allocator& alloc = Allocator() );is strongly preferred and selected - The template parameter of
std::initializer_list<T>is deduced tostd::pair<int, std::string> - Each element of
foois astd::pair. However,std::pairhas no overload acceptingstd::initializer_list.
I am not so sure about step 3. I know the inner braces can't be interpreted as std::initializer_list since they are heterogenous. What mechanism in the standard is actually constructing each element of foo? I suspect the answer has something to do with forwarding the arguments in the inner braces to the overload template< class U1, class U2 pair( U1&& x, U2&& y ); but I don't know what this is called.
EDIT:
I figure a simpler way to ask the same question would be: When one does
std::map<int, std::string> m = { // nested list-initialization
{1, "a"},
{2, {'a', 'b', 'c'} },
{3, s1}
as shown in the cppreference example, where in the standard does it say that {1, "a"}, {2, {'a', 'b', 'c'} }, and {3, s1} each get forwarded to the constructor for std::pair<int, std::string>?
Usually, expressions are analyzed inside-out: The inner expressions have types and these types then decide which meaning the outer operators have and which functions are to be called.
But initializer lists are not expressions, and have no type. Therefore, inside-out does not work. Special overload resolution rules are needed to account for initializer lists.
The first rule is: If there are constructors with a single parameter that is some
initializer_list<T>, then in a first round of overload resolution only such constructors are considered (over.match.list).The second rule is: For each
initializer_list<T>candidate (there could be more than one of them per class, with differentTeach), it is checked that each initializer can be converted toT, and only those candidates remain where this works out (over.ics.list).This second rule is basically, where the initializer-lists-have-no-type hurdle is taken and inside-out analysis is resumed.
Once overload resolution has decided that a particular
initializer_list<T>constructor should be used, copy-initialization is used to initialize the elements of typeTof the initializer list.