Consider the snippet:
#include <unordered_map> void foo(const std::unordered_map<int,int> &) {} int main() { foo({}); } This fails with GCC 4.9.2 with the message:
map2.cpp:7:19: error: converting to ‘const std::unordered_map<int, int>’ from initializer list would use explicit constructor ‘std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map(std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type, const hasher&, const key_equal&, const allocator_type&) [with _Key = int; _Tp = int; _Hash = std::hash<int>; _Pred = std::equal_to<int>; _Alloc = std::allocator<std::pair<const int, int> >; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type = long unsigned int; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::hasher = std::hash<int>; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::key_equal = std::equal_to<int>; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::allocator_type = std::allocator<std::pair<const int, int> >]’ Testing with other compiler/library implementations:
A couple of more baffling points:
std::unordered_map with std::map makes the error go away,foo({}) with foo foo({{}}) also makes the error go away.Also, replacing {} with a non-empty initializer list works as expected in all cases.
So my main questions are:
foo({{}}) exactly do to make the error go away?EDIT fixed a couple of typos.
The indirect initialization syntax with a braced-init-list your code is using is called copy-list-initialization.
The overload resolution procedure selecting the best viable constructor for that case is described in the following section of the C++ Standard:
§ 13.3.1.7 Initialization by list-initialization
[over.match.list]
When objects of non-aggregate class type
Tare 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
Tand 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
Tand the argument list consists of the elements of the initializer list.If the initializer list has no elements and
Thas a default constructor, the first phase is omitted. In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed. [ Note: This differs from other situations (13.3.1.3, 13.3.1.4), where only converting constructors are considered for copy-initialization. This restriction only applies if this initialization is part of the final result of overload resolution. — end note ].
According to that, an initializer-list-constructor (the one callable with a single argument matching the constructor's parameter of type std::initializer_list<T>) is usually preferred to other constructors, but not if a default-constructor is available, and the braced-init-list used for list-initialization is empty.
What is important here, the set of constructors of the standard library's containers has changed between C++11 and C++14 due to LWG issue 2193. In case of std::unordered_map, for the sake of our analysis, we are interested in the following difference:
C++11:
explicit unordered_map(size_type n = /* impl-defined */, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& alloc = allocator_type()); unordered_map(initializer_list<value_type> il, size_type n = /* impl-defined */, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& alloc = allocator_type()); C++14:
unordered_map(); explicit unordered_map(size_type n, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& alloc = allocator_type()); unordered_map(initializer_list<value_type> il, size_type n = /* impl-defined */, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& alloc = allocator_type()); In other words, there is a different default constructor (the one that can be called without arguments) depending on the language standard (C++11/C++14), and, what is crucial, the default constructor in C++14 is now made non-explicit.
That change was introduced so that one can say:
std::unordered_map<int,int> m = {}; or:
std::unordered_map<int,int> foo() { return {}; } which are both semantically equivalent to your code (passing {} as the argument of a function call to initialize std::unordered_map<int,int>).
That is, in case of a C++11-conforming library, the error is expected, as the selected (default) constructor is explicit, therefore the code is ill-formed:
explicit unordered_map(size_type n = /* impl-defined */, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& alloc = allocator_type()); In case of a C++14-conforming library, the error is not expected, as the selected (default) constructor is not explicit, and the code is well-formed:
unordered_map(); As such, the different behavior you encounter is solely related to the version of libstdc++ and libc++ you are using with different compilers/compiler options.
Replacing
std::unordered_mapwithstd::mapmakes the error go away. Why?
I suspect it's just because std::map in the libstdc++ version you are using was already updated for C++14.
Replacing
foo({})withfoo({{}})also makes the error go away. Why?
Because now this is copy-list-initialization {{}} with a non-empty braced-init-list (that is, it has one element inside, initialized with an empty braced-init-list {}), so the rule from the first phase of § 13.3.1.7 [over.match.list]/p1 (quoted before) that prefers an initializer-list-constructor to other ones is applied. That constructor is not explicit, hence the call is well-formed.
Replacing
{}with a non-empty initializer list works as expected in all cases. Why?
Same as above, the overload resolution ends up with the first phase of § 13.3.1.7 [over.match.list]/p1.
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