Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit conversion failure from initializer list

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 with std::map makes the error go away,
  • replacing 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:

  • 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.

like image 304
bluescarni Avatar asked Nov 15 '14 15:11

bluescarni


Video Answer


1 Answers

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]

  1. 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.

If the initializer list has no elements and T has 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_map with std::map makes 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({}) with foo({{}}) 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.

like image 78
Piotr Skotnicki Avatar answered Oct 04 '22 16:10

Piotr Skotnicki