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::pair
s. 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!
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 classT
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 classT
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With