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

别等时光非礼了梦想. 提交于 2019-11-26 16:50:22

问题


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

auto var = { 1, 2, 3 };   // type deduced as std::initializer_list<int>

template<class T> void f(T parameter);

f({ 1, 2, 3 });          // doesn't compile; type deduction fails

I even know where this is specified in the C++11 standard: 14.8.2.5/5 bullet 5:

[It's a non-deduced context if the program has] A function parameter for which the associated argument is an initializer list (8.5.4) but the parameter does not have std::initializer_list or reference to possibly cv-qualified std::initializer_list type. [ Example:

template void g(T);

g({1,2,3}); // error: no argument deduced for T

end example ]

What I don't know or understand is why this difference in type deduction behavior exists. The specification in the C++14 CD is the same as in C++11, so presumably the standardization committee doesn't view the C++11 behavior as a defect.

Does anybody know why auto deduces a type for a braced initializer, but templates are not permitted to? While speculative explanations of the form "this could be the reason" are interesting, I'm especially interested in explanations from people who know why the standard was written the way it was.


回答1:


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




回答2:


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




回答3:


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.



来源:https://stackoverflow.com/questions/17582667/why-do-auto-and-template-type-deduction-differ-for-braced-initializers

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!