Implicit conversion failure from initializer list

后端 未结 2 1666
再見小時候
再見小時候 2020-12-13 08:54

Consider the snippet:

#include 

void foo(const std::unordered_map &) {}

int main()
{
        foo({});
}


        
相关标签:
2条回答
  • 2020-12-13 09:29

    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.

    0 讨论(0)
  • 2020-12-13 09:32

    List-initialization for references is defined as follows, [dcl.init.list]/3:

    Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is copy-list-initialized or direct-list-initialized, depending on the kind of initialization for the reference, and the reference is bound to that temporary.

    So your code fails because

    std::unordered_map<int,int> m = {};
    

    fails. List-initialization for this case is covered through this bullet from [dcl.init.list]/3:

    Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.

    So the object's default constructor will be called1.
    Now to the crucial bits: In C++11, unordered_map had this default constructor2:

    explicit unordered_map(size_type n = /* some value */ ,
                           const hasher& hf = hasher(),
                           const key_equal& eql = key_equal(),
                           const allocator_type& a = allocator_type());
    

    Clearly, calling this explicit constructor through copy-list-initialization is ill-formed, [over.match.list]:

    In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.

    Since C++14 unordered_map declares a default constructor that is non-explicit:

    unordered_map();
    

    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:

    To value-initialize an object of type T means:
    — if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called […];

    2) [class.ctor]/4:

    A default constructor for a class X is a constructor of class X that can be called without an argument.

    0 讨论(0)
提交回复
热议问题