List-initialization and failed overload resolution of initializer_list constructor

前端 未结 1 1866
醉话见心
醉话见心 2021-02-05 22:26

The below fails to compile with clang35 -std=c++11:

#include 
#include 
#include 

class A
{
          


        
1条回答
  •  栀梦
    栀梦 (楼主)
    2021-02-05 23:23

    The behavior makes sense. Scott Meyers has an example almost exactly like this in Effective Modern C++ (emphasis in original):

    If, however, one or more constructors declare a parameter of type std::initializer_list, calls using the braced initialization syntax strongly prefer the overloads taking std;:initializer_lists. Strongly. If there's any way for compilers to construe a call using a braced initializer to be a constructor taking a std::initializer_list, compilers will employ that interpretation.

    Example using this class:

    class Widget {
    public:
        Widget(int, bool);
        Widget(int, double);
        Widget(std::initializer_list);
    };
    
    Widget w1(10, true); // calls first ctor
    Widget w2{10, true}; // calls std::initializer_list ctor
    Widget w3(10, 5.0); // calls second ctor
    Widget w4{10, 5.0}; // calls std::initializer_list ctor
    

    Those two calls call the initializer_list ctor even though they involve converting BOTH arguments - and even though the other constructors are perfect matches.

    Furthermore:

    Compilers' determination to match braced initializers with constructors taking std::initializer_lists is so strong, it prevails even if the best-match std::initializer_list constructor can't be called. For example:

    class Widget {
    public:
        Widget(int, bool); // as before
        Widget(int, double); // as before
        Widget(std::initializer_list ); // now bool
    };
    
    Widget w{10, 5.0}; // error! requires narrowing conversions
    

    Both compilers pick the correct overload (the initializer_list one) - which we can see is required from the standard (§13.3.1.7):

    When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:

    (1.1) — 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.
    (1.2) — 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.

    But calling that particular constructor involves a narrowing. In 8.5.1:

    If the initializer-clause is an expression and a narrowing conversion (8.5.4) is required to convert the expression, the program is ill-formed.

    So the program is ill-formed. In this case, clang chooses to throw an error while gcc chooses to issue a warning. Both compilers are conforming.

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