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:
- GCC < 4.9 accepts this without complaining,
- Clang 3.5 with libstdc++ fails with a similar message,
- Clang 3.5 with libc++ accepts this,
- ICC 15.something accepts this (not sure which standard library it is using).
A couple of more baffling points:
- replacing
std::unordered_map
withstd::map
makes the error go away, - replacing
foo({})
with foofoo({{}})
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:
- who is right here? Is the code above well-formed?
- What does the syntax with double curly braces
foo({{}})
exactly do to make the error go away?
EDIT fixed a couple of typos.
List-initialization for references is defined as follows, [dcl.init.list]/3:
So your code fails because
fails. List-initialization for this case is covered through this bullet from [dcl.init.list]/3:
So the object's default constructor will be called1.
Now to the crucial bits: In C++11,
unordered_map
had this default constructor2:Clearly, calling this
explicit
constructor through copy-list-initialization is ill-formed, [over.match.list]:Since C++14
unordered_map
declares a default constructor that is non-explicit:So a C++14 standard library implementation should compile this without problems. Presumably libc++ is already updated, but libstdc++ is lagging behind.
1) [dcl.init]/7:
2) [class.ctor]/4:
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:
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:
C++14:
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:
or:
which are both semantically equivalent to your code (passing
{}
as the argument of a function call to initializestd::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: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: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.
I suspect it's just because
std::map
in the libstdc++ version you are using was already updated for C++14.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 notexplicit
, hence the call is well-formed.Same as above, the overload resolution ends up with the first phase of § 13.3.1.7 [over.match.list]/p1.