Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What language rules permit C++11 to deduce that this is an initializer_list of pairs?

In C++11, it seems like it's legal to initialize a std::map<std::string, int> as follows:

std::map<std::string, int> myMap = {
     { "One",   1 },
     { "Two",   2 },
     { "Three", 3 }
};

Intuitively, this makes sense - the brace-enclosed initializer is a list of pairs of strings, and std::map<std::string, int>::value_type is std::pair<std::string, int> (possibly with some const qualifications.

However, I'm not sure I understand how the typing works here. If we eliminate the variable declaration here and just have the brace-enclosed initializer, the compiler wouldn't know that it was looking at a std::initializer_list<std::pair<std::string, int>> because it wouldn't know that the braced pairs represented std::pairs. Therefore, it seems as though the compiler is somehow deferring the act of assigning a type to the brace-enclosed initializer until it has enough type information from the std::map constructor to realize that the nested braces are for pairs. I don't remember anything like this happening in C++03; to the best of my knowledge, the type of an expression never depended on its context.

What language rules permit this code to compile correctly and for the compiler to determine what type to use for the initializer list? I'm hoping for answers with specific references to the C++11 spec, since it's really interesting that this works!

Thanks!

like image 989
templatetypedef Avatar asked Jul 13 '14 00:07

templatetypedef


1 Answers

In the expression

std::map<std::string, int> myMap = {
     { "One",   1 },
     { "Two",   2 },
     { "Three", 3 }
};

on the right side you have a braced-init-list where each element is also a braced-init-list. The first thing that happens is that the initializer list constructor of std::map is considered.

map(initializer_list<value_type>,
    const Compare& = Compare(),
    const Allocator& = Allocator());

map<K, V>::value_type is a typedef for pair<const K, V>, in this case pair<const string, int>. The inner braced-init-lists can be successfully converted to map::value_type because std::pair has a constructor that takes references to its two constituent types, and std::string has an implicit conversion constructor that takes a char const *.

Thus the initializer list constructor of std::map is viable, and the construction can happen from the nested braced-init-lists.

The relevant standardese is present in §13.3.1.7/1 [over.match.list]

When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:
— Initially, the candidate functions are the initializer-list constructors (8.5.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.

The first bullet is what causes the initializer_list constructor of map to be selected for the outer braced-init-list, while the second bullet results in the selection of the correct pair constructor for the inner braced-init-lists.

like image 160
Praetorian Avatar answered Sep 24 '22 23:09

Praetorian