Why do auto and template type deduction differ for braced initializers?

前端 未结 3 2051
伪装坚强ぢ
伪装坚强ぢ 2020-11-29 09:23

I understand that, given a braced initializer, auto will deduce a type of std::initializer_list, while template type deduction will fail:



        
相关标签:
3条回答
  • 2020-11-29 10:05

    The reason is described in N2640:

    A {}-list cannot deduce against a plain type parameter T. For example:

    template<class T> void count(T); // (1).
    struct Dimensions { Dimensions(int, int); };
    size_t count(Dimensions); // (2).
    size_t n = count({1, 2}); // Calls (2); deduction doesn't
                              // succeed for (1).
    

    Another example:

    template<class T>
    void inc(T, int); // (1)
    template<class T>
    void inc(std::initializer_list<T>, long); // (2)
    inc({1, 2, 3}, 3); // Calls (2). (If deduction had succeeded
                       // for (1), (1) would have been called — a
                       // surprise.)
    

    On the other hand, being able to deduce an initializer_list<X> for T is attractive to allow:

    auto x = { 1, 1, 2, 3, 5 };
    f(x);
    g(x);
    

    which was deemed desirable behavior since the very beginning of the EWG discussions about initializer lists.

    Rather than coming up with a clever deduction rule for a parameter type T matched with a {}-list (an option we pursued in earlier sketches and drafts of this paper), we now prefer to handle this with a special case for "auto" variable deduction when the initializer is a {}-list. I.e., for the specific case of a variable declared with an "auto" type specifier and a {}-list initializer, the "auto" is deduced as for a function f(initializer_list<T>) instead of as for a function f(T).

    For conclusion, the problem is that if we allow a {}-list to deduce against a plain type parameter T, then the function with parameter T would have very high priority during overload resolution, which may cause wired behavior (like the examples above).

    0 讨论(0)
  • 2020-11-29 10:05

    First of all it's "speculative explanations of the form "this could be the reason"" as you call it.

    {1,2,3} is not only std::initializer_list<int> but also allow initialize types without constructor. For example:

    #include <initializer_list>
    
    struct x{
        int a,b,c;
    };
    
    void f(x){
    
    }
    int main() {
        f({1,2,3});
    }
    

    is correct code. To show that it isn't initializer_list let's see the following code:

    #include <initializer_list>
    
    struct x{int a,b,c;};
    
    void f(x){
    
    }
    int main() {
        auto il = {1, 2, 3};
        f(il);
    }
    

    Error is:

    prog.cpp: In function ‘int main()’:
    prog.cpp:10:9: error: could not convert ‘il’ from ‘std::initializer_list<int>’ to ‘x’
    

    And now to the question "What is the difference?"

    in auto x = {1, 2, 3}; code it's OK to determine type, because coder explicitly said "It's not important what's type it is" using auto

    While in case of function template he may be sure that he is using different type. And it's good to prevent errors in ambiguous cases (It doesn't seem like C++ style , through).

    Especially bad it will be in case when there was 1 function f(x) and then it was changed to template one. Programmer wrote to use it as x, and after adding new function for other type it slightly change to call completely different one.

    0 讨论(0)
  • 2020-11-29 10:08

    There are two important reasons for templates not to do any deduction (the two that I remember in a discussion with the guy in charge)

    • Concerns about future language extensions (there are multiple meanings you could invent - what about if we wanted to introduce perfect forwarding for braced init list function arguments?)

    • The braces can sometimes validly initialize a function parameter that is dependent

    template<typename T>
    void assign(T &d, const T& s);
    
    int main() {
      vector<int> v;
      assign(v, { 1, 2, 3 });
    }
    

    If T would be deduced at the right side to initializer_list<int> but at the left side to vector<int>, this would fail to work because of a contradictional argument deduction.

    The deduction for auto to initializer_list<T> is controversial. There exist a proposal for C++-after-14 to remove it (and to ban initialization with { } or {a, b}, and to make {a} deduce to the type of a).

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