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

3

20

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.

Clement answered 10/7, 2013 at 23:29 Comment(3)
There was another SO question asked recently on what you figured out. It might have some why in it.Homely
Ok, found it. The only real why discussion is one comment, but you can take a look: #17496768Homely
Quote from Scott Meyers: "I have no idea why type deduction for auto and for templates is not identical. If you know, please tell me!".Broadax
P
15

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

Perspiration answered 12/7, 2013 at 19:37 Comment(0)
F
3

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

Fuge answered 22/7, 2018 at 14:49 Comment(0)
M
0

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.

Mario answered 11/7, 2013 at 20:50 Comment(3)
I don't understand how saying auto means "it's not important what the type is," but saying template<typename T> means something different. In both cases, you run the risk that the type deduced may not be what the programmer intended.Clement
I dont understand the second code. You write "To show that it isnt initializer list" but in the example {1,2,3} is a initializer list, hence the error...Troposphere
I mean I'm showing that in first code it's not initializer_list. Because when it's initialzer_list it doesn't workMario

© 2022 - 2024 — McMap. All rights reserved.