User-defined conversions aren't applied to variadic function arguments? Why not? - c++

I have a class that mainly wraps a std::variant with some minor additional functionality/meta-data.
For simplicity of use, I wanted to provide a user-defined conversion of this wrapper class to the underlying variant type, so functions like std::holds_alternative could be called directly on it.
What I've uncovered leaves me very confused about whether and when user-defined conversions will be applied. Here is simplified code.
#include <iostream>
#include <variant>
struct MyClass
{
// "MyClass" is implicitly convertible to a variant<bool,int>
operator std::variant <bool, int> ()
{
return std::variant<bool,int>(5);
}
};
void thisFunctionTakesOneSpecificVariantType (std::variant<bool,int> v)
{
std::cout << v.index();
}
template <class... types>
void thisFunctionTakesAnyVariantType (std::variant<types...> v)
{
std::cout << v.index();
}
int main ()
{
MyClass mc;
// 1. This compiles and runs as expected,
// proving the user-defined conversion (MyClass -> variant<int,bool>) exists and works "sometimes"
thisFunctionTakesOneSpecificVariantType (mc);
// 2. This compiles and runs as expected,
// proving "thisFunctionTakesAnyVariantType" is well defined
thisFunctionTakesAnyVariantType (std::variant <bool, int> (5));
// 3. But, combining 1 & 2, this fails to compile:
/* fails */ thisFunctionTakesAnyVariantType (mc); // error: no matching function for call to 'thisFunctionTakesAnyVariantType'
// 4. This is what i really want to do, and it also fails to compile
/* fails */ std::holds_alternative<int>(mc); // error: no matching function for call to 'holds_alternative'
// 5. An explicit conversion works for 3 and 4, but why not an implicit conversion?
// After all, the implicit conversion worked in #1
thisFunctionTakesAnyVariantType ( static_cast<std::variant <bool, int>> (mc) );
return EXIT_SUCCESS;
}
Why don't use cases 3 and 4 compile, while 1, 2, and 5 do?
In the error messages, it provides this note:
note: candidate template ignored: could not match 'variant<type-parameter-0-1...>' against 'MyClass'
inline constexpr bool holds_alternative(const variant<_Types...>& __v)

Why don't use cases 3 compile
Because implicit conversions are not considered in template argument deduction:
Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.
The conversion from MyClass to std::variant <bool, int> won't be considered then type deduction fails. As #5 showed you can apply explicit conversion before passing to thisFunctionTakesAnyVariantType.
Why don't use cases 4 compile
Same reason as #3. Note that even you specify some template arguments for the parameter pack template argument deduction still tries to deduce the following template arguments from the function argument. You can use exclude the function parameter from the deduction as
template <class... types>
void thisFunctionTakesAnyVariantType (std::type_identity_t<std::variant<types...>> v)
then you can call it as
thisFunctionTakesAnyVariantType<bool, int>(mc);
But note that this will make all the template argument deduction invalid (and #2 and 5 would fail), so it might be a bad idea.
BTW: std::type_identity is supported since C++20 even it's easy to implement one.

That's how the rules are laid out. Someone else with more knowledge then me might come and give you the exact rules at play here (template substitution and conversions considered), but at the end of the day it is what it is and you cannot change that.
In order for the conversion to be considered your class needs to inherit from std::variant<int, bool>. All of your examples will compile. I am however reluctant to recommend this approach as indeed composition seems the right design here.
What I would do is provide a conversion method. You lose the implicit aspect of it, but that maybe it's not such a bad idea, at least considering the alternative.
struct MyClass
{
std::variant<bool, int> to_var() const
{
return {5};
}
}
thisFunctionTakesAnyVariantType (mc.to_var());
std::holds_alternative<int>(mc.to_var());
You could leave the conversion operator for those situation where it works but consider if it doesn't just add confusion.

Related

return a constructible from a variant being constructed from an alternative

Is it okay to have a struct that is constructible from a variant (say, from std::variant), and to return such structure implicitly constructed from one of the variant's alternatives?
Consider the following code:
#include <utility>
struct type1_t
{
};
struct type2_t
{
};
struct variant
{
/*template <typename T>
variant(T&& t)
{}*/
variant(type1_t){}
};
struct var_holder_t
{
var_holder_t(variant v)
: m_var(std::move(v))
{}
private:
variant m_var;
};
var_holder_t foo()
{
return type1_t{ }; // <====== offending line
}
MSVC 2017, 2019 allow that, but GCC and clang does not compile. Changing to the other variant's constructor or even using boost::variant or std::variant doesn't help.
Worth noting that changing the offending line to return {type1_t{ }}; makes it compile somehow. Currently I'm lost, though technically speaking adding two extra {} doesn't do much harm readability-wise, probably I'll stick to that at the moment, just wish for an answer.
Also making var_holder_t's constructor template (forwarding) helps, and it makes var_holder_t constructible from type1_t directly (and very likely a lot better performance-wise), but at the moment I want to keep var_holder_t completely non-template - it's supposed (thought, designed) to be casually-written simple code.
Update: what actually confused me is that Clang emits this message:
note: candidate constructor not viable: no known conversion from 'type1_t' to 'variant' for 1st argument var_holder_t(variant v)
Which is weird, because clearly there is a conversion from type1_t to variant, but looks like its just bogus diagnostics. To put it together, I think I can come up with a simple reason why the construction above does not work: it requires two implicit conversions, but the rules are that only one is considered.
The below statement works because list initialization is being performed.
return {type1_t{ }};
copy-list-initialization
return { arg1, arg2, ... } ; (8)
List initialization is performed in the following situations:
...
direct-list-initialization (both explicit and non-explicit constructors are considered)
...
8) in a return statement with braced-init-list used as the return expression and list-initialization initializes the returned object
Here var_holder_t is initialized with a type1_t object and it works as expected.

Could not convert from brace-enclosed initializer list to std tuple

As part of a bigger project, I'm playing with std::tuple and templates; consider the following code:
template <typename ...T> void foo(tuple<T...> t) {}
void bar(tuple<int, char> t) {}
tuple<int, char> quxx() { return {1, 'S'}; }
int main(int argc, char const *argv[])
{
foo({1, 'S'}); // error
foo(make_tuple(1, 'S')); // ok
bar({1, 'S'}); // ok
quxx(); // ok
return 0;
}
According to this answer C++17 supports tuple initialization from copy-list-initialization, however it seems such support is limited since I get the following error (GCC 7.2.0):
main.cpp: In function 'int main(int, const char**)':
main.cpp:14:17: error: could not convert '{1, 'S'}' from '<brace-enclosed initializer list>' to 'std::tuple<>'
foo({1, 'S'}); // error
^
Is there any way I can use brace-enclosed syntax in this scenario?
Some Context : this is going to be used in an operator overload so I guess I'm bound to tuples and cannot make use of variadics, any hint is well-accepted.
Extra : Clang 6 also complains
prog.cc:12:5: error: no matching function for call to 'foo'
foo({1, 'S'}); // error
^~~
prog.cc:6:31: note: candidate function [with T = <>] not viable: cannot convert initializer list argument to 'tuple<>'
template <typename ...T> void foo(tuple<T...> t) {}
A braced-init-list, like {1, 'S'}, does not actually have a type. In the context of template deduction, you can only use them in certain cases - when deducing against initializer_list<T> (where T is a function template parameter) or when the corresponding parameter is already deduced by something else. In this case, neither of those two things is true - so the compiler cannot figure out what ...T is supposed to be.
So you can provide the types directly:
foo<int, char>({1, 'S'});
Or you can construct the tuple yourself and pass that in:
foo(std::tuple<int, char>(1, 'S')); // most explicit
foo(std::tuple(1, 'S')); // via class template argument deduction
Today, ClassTemplate<Ts...> can only be deduced from expressions of type ClassTemplate<Us...> or types that inherit from something like that. A hypothetical proposal could extend that to additionally try to perform class template argument deduction on the expression to see if that deduction succeeds. In this case, {1, 'S'} isn't a tuple<Ts...> but tuple __var{1, 'S'} does successfully deduce tuple<int, char> so that would work. Such a proposal would also have to address issues like... what if we're deducing ClassTemplate<T, Ts...> or any minor variation, which isn't something that class template argument deduction allows (but is something that many people have at times expressed interest in being able to do).
I'm not aware of such a proposal today.
According to this answer C++17 supports tuple initialization from copy-list-initialization, however it seems such support is limited since I get the following error
The problem is another.
When you call bar({1, 'S'}), the compiler knows that bar() receive a tuple<int, char>, so take 1 as int and 'S' as char.
See another example: if you define
void baz (std::tuple<int> const &)
{ }
you can call
baz(1);
because the compiler knows that baz() receive a std::tuple<int> so take 1 to initialize the int in the tuple.
But with
template <typename ...T>
void foo(tuple<T...> t)
{ }
the compiler doesn't know the T... types; when you call
foo({1, 'S'});
what T... types should deduce the compiler?
I see, at least, two hypothesis: T = int, char or T = std::pair<int, char>; or also T = std::tuple<int, char>.
Which hypothesis should follows the compiler?
I mean: if you pass a std::tuple to foo(), the compiler accept the list of types in the tuple as the list of T...; but if you pass something else, the compiler must deduce the correct std::tuple; but this deduction, in this case, is not unique. So the error.

Variadic template issue

I have the following code I'm trying to adapt for my own purposes. This is a learning exercise for me to attempt an update of my C++ skills.
This was originally written with Clang 3.1 in mind as far as I can tell.
I've tried compiling with Clang versions from 3.1 to 4.0 and GCC 4.9 to 7.1 with very similar results.
These are the error messages from GCC 5.1
Error 1: In instantiation of 'constexpr Struct<Fields>::Struct(T&& ...) [with T = {Struct<foo<int>, bar<double> >&}; Fields = {foo<int>, bar<double>}]':
<source>:46:12: required from here
Error 2: <source>:28:61: error: mismatched argument pack lengths while expanding 'Fields'
constexpr Struct(T &&...x) : Fields{static_cast<T&&>(x)}... {}
Please ELI5 if you have the patience :P
You can see this in godbolts compiler doohickey here:
https://godbolt.org/g/2dRPXf
EDIT:
Given the answers by #Passer-By and #Daniel-Jour, I wonder if Struct(Struct const &) = default; is even necessary. Will removing this constructor from Struct have effects of which I am not aware or do no foresee (I am no C++ Swami!)?
Is the constructor constexpr Struct(T &&...x) : Fields{static_cast<T&&>(x)}... {} a good stand-in for what would otherwise be generated by Struct(Struct const &) = default;?
I'm feeling pretty ambiguous about either proposed solution so far.
End EDIT
// From http://duriansoftware.com/joe/Self-aware-struct-like-types-in-C++11.html
// edited a bit to stop the compiler whining about comments in multiline macros
#include <type_traits>
#include <utility>
#define SELFAWARE_IDENTIFIER(NAME) \
template<typename T> \
struct NAME { \
T NAME; /* field name */ \
constexpr static char const *name() { return #NAME; } /* field type */ \
using type = T; /* field value generic accessor */ \
T &value() & { return this->NAME; } \
T const &value() const & { return this->NAME; } \
T &&value() && { return this->NAME; } \
};
template<typename...Fields>
struct Struct : Fields... {
// A convenience alias for subclasses
using struct_type = Struct;
// Preserve default constructors
Struct() = default;
Struct(Struct const &) = default;
// Forwarding elementwise constructor
template<typename...T>
constexpr Struct(T &&...x) : Fields{static_cast<T&&>(x)}... {} // Error 2 here
};
SELFAWARE_IDENTIFIER(foo)
SELFAWARE_IDENTIFIER(bar)
// Aliasing a Struct instance
using FooBar = Struct<foo<int>, bar<double> >;
// Inheriting a Struct instance (requires inheriting constructors)
struct FooBar2 : Struct<foo<int>, bar<double>> { using struct_type::struct_type; };
static_assert(std::is_trivial<FooBar>::value, "should be trivial");
static_assert(FooBar{2, 3.0}.foo + FooBar{2, 4.0}.foo == 4, "2 + 2 == 4");
FooBar frob(int x) {
FooBar f = {x, 0.0};
f.foo += 1;
f.bar += 1.0;
return f; // Error 1 here
}
You've fallen victim of what I know as "too perfect forwarding".
To debug this, first look closely at the error message:
instantiation of constexpr Struct<Fields>::Struct(T&& ...) [with T = {Struct<foo<int>, bar<double> >&}; Fields = {foo<int>, bar<double>}]:
This tells us that this line of code
return f;
does not as expected call the copy or move constructor, but rather the perfect forwarding constructor.
Looking at what this constructor does, it's clear that this constructor is not capable of copying or moving from a Struct. Its intended​ use case is to construct each of the fields from one of the arguments. But the error message shows that there is only a single argument of type Struct<foo<int>, bar<double> >&. Because the expansion of the arguments also expands Fields (of which there are two) you get that second error:
[..] error: mismatched argument pack lengths [..]
But why does it take the perfect forwarding constructor instead of the also available copy constructor? Because the forwarding constructor is able to provide a better candidate (an exact match actually) than the copy constructor (whose signature is Struct(Struct const &)): Struct(Struct & &&), which according to the reference combination rules is Struct(Struct &). And that's exactly what's needed to use f in return f;: after all f is a non const lvalue.
One possibility to fix this is to provide another (copy) constructor with the exact signature:
Struct(Struct & s)
: Struct(static_cast<Struct const &>(s))
{}
But if you also add in volatile you need to write a total of six constructors to have all cases covered. Not nice.
The better solution is to exclude the perfect forwarding constructor from overload resolution with SFINAE:
template<typename T>
using decay_t = typename decay<T>::type;
template<
typename...T,
std::enable_if<
(sizeof...(T) == sizeof...(Fields))
&& not_self_helper<Struct, std::tuple<decay_t<T>...>>::value
>::type * = nullptr
>
constexpr Struct(T &&...x)
: Fields{static_cast<T&&>(x)}... {}
not_self_helper checks whether a single parameter passed to a structure with a single field is of the structures own type:
template<typename, typename>
struct not_self_helper : std::true_type {};
template<typename Self>
struct not_self_helper<Self, std::tuple<Self>> : std::false_type {};
This fixes your main issue: The forwarding constructor is semantically just wrong. It does not take an arbitrary number of parameters, but needs exactly the same number of parameters as the structure has fields. Further, none of the fields should be constructed from the containing structure itself (recursive membership means infinite structure size, after all). I shortened that test to only check when there's a single argument, though. Strictly speaking, this is semantically wrong, but in practice it covers the most "dangerous" case: When your forwarding constructor is wrongfully selected for copy/move construction.
The problem consists of two parts
Overload resolution
For a variadic constructor with forwarding references
template<typename... Args>
Struct(Args&&...);
Given any lvalue arguments, Args will be deduced as lvalue references, and rvalue arguments rvalue references.
For another constructor to be called, the variadic constrcutor must not have a better conversion sequence than the constructor.
Your code includes only one other constructor that takes one parameter, the copy constructor
Struct(const Struct&);
A lvalue const Struct will bind with the copy constructor's const Struct& while a lvalue or rvalue Struct will bind with the variadic constructor's Struct& or Struct&& respectively.
Value category of returned type
A glvalue expression referring to an automatic duration variable declared in the function in a return statement is considered a xvalue, and would therefore bind to Struct&& first. When that fails, the expression will be considered a lvalue and proceed to bind to Struct& or const Struct&.
In this case, Struct&& succeeds with the variadic constructor, but results in an error.
If a move constructor is provided, the move constructor will be selected after overload resolution discards the variadic constructor.
Responding to edit
Removing your user-provided copy constructor will allow for an implicitly declared move constructor, which will make the code compile, following the reasons above.
The variadic constructor is not a good stand in for either constructors, aside from being semantically wrong, it takes arbitrary arguments but requires a fixed (in this case 2) amount of arguments to initialize the fields. If it were to be used as a copy constructor, you will get the same compile error: mismatched argument pack lengths while expanding 'Fields'
As was mentioned in Daniel Jour's answer, you should probably put some SFINAE in the variadic constructor to alleviate some pain.

std::function fails to distinguish overloaded functions

I am trying to understand why std::function is not able to distinguish between overloaded functions.
#include <functional>
void add(int,int){}
class A {};
void add (A, A){}
int main(){
std::function <void(int, int)> func = add;
}
In the code shown above, function<void(int, int)> can match only one of these functions and yet it fails. Why is this so? I know I can work around this by using a lambda or a function pointer to the actual function and then storing the function pointer in function. But why does this fail? Isn't the context clear on which function I want to be chosen? Please help me understand why this fails as I am not able to understand why template matching fails in this case.
The compiler errors that I get on clang for this are as follows:
test.cpp:10:33: error: no viable conversion from '<overloaded function type>' to
'std::function<void (int, int)>'
std::function <void(int, int)> func = add;
^ ~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1266:31: note:
candidate constructor not viable: no overload of 'add' matching
'std::__1::nullptr_t' for 1st argument
_LIBCPP_INLINE_VISIBILITY function(nullptr_t) : __f_(0) {}
^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1267:5: note:
candidate constructor not viable: no overload of 'add' matching 'const
std::__1::function<void (int, int)> &' for 1st argument
function(const function&);
^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1269:7: note:
candidate template ignored: couldn't infer template argument '_Fp'
function(_Fp,
^
1 error generated.
EDIT - In addition to MSalters' answer, I did some searching on this forum and found the exact reason why this fails. I got the answer from Nawaz's reply in this post.
I have copy pasted from his answer here:
int test(const std::string&) {
return 0;
}
int test(const std::string*) {
return 0;
}
typedef int (*funtype)(const std::string&);
funtype fun = test; //no cast required now!
std::function<int(const std::string&)> func = fun; //no cast!
So why std::function<int(const std::string&)> does not work the way funtype fun = test works above?
Well the answer is, because std::function can be initialized with any object, as its constructor is templatized which is independent of the template argument you passed to std::function.
It's obvious to us which function you intend to be chosen, but the compiler has to follow the rules of C++ not use clever leaps of logic (or even not so clever ones, as in simple cases like this!)
The relevant constructor of std::function is:
template<class F> function(F f);
which is a template that accepts any type.
The C++14 standard does constrain the template (since LWG DR 2132) so that it:
shall not participate in overload resolution unless f is Callable (20.9.12.2) for argument types ArgTypes... and return type R.
which means that the compiler will only allow the constructor to be called when Functor is compatible with the call signature of the std::function (which is void(int, int) in your example). In theory that should mean that void add(A, A) is not a viable argument and so "obviously" you intended to use void add(int, int).
However, the compiler can't test the "f is Callable for argument types ..." constraint until it knows the type of f, which means it needs to have already disambiguated between void add(int, int) and void add(A, A) before it can apply the constraint that would allow it to reject one of those functions!
So there's a chicken and egg situation, which unfortunately means that you need to help the compiler out by specifying exactly which overload of add you want to use, and then the compiler can apply the constraint and (rather redundantly) decide that it is an acceptable argument for the constructor.
It is conceivable that we could change C++ so that in cases like this all the overloaded functions are tested against the constraint (so we don't need to know which one to test before testing it) and if only one is viable then use that one, but that's not how C++ works.
While it's obvious what you want, the problem is that std::function cannot influence overload resolution of &add. If you were to initialize a raw function pointer (void (*func)(int,int) = &add), it does work. That's because function pointer initialization is a context in which overload resolution is done. The target type is exactly known. But std::function will take almost any argument that's callable. That flexibility in accepting arguments does mean that you can't do overload resolution on &add. Multiple overloads of add might be suitable.
An explicit cast will work, i.e. static_cast<void(*)(int, int)> (&add).
This can be wrapped in a template<typename F> std::function<F> make_function(F*) which would allow you to write auto func = make_function<int(int,int)> (&add)
Try:
std::function <void(int, int)> func = static_cast<void(*)(int, int)> (add);
Addresses to void add(A, A) and void add(int, int) obvoiusly differes. When you point to the function by name it is pretty much imposible for compiler to know which function address do you need. void(int, int) here is not a hint.
Another way to deal with this is with a generic lambda in C++14:
int main() {
std::function<void(int, int)> func = [](auto &&... args) {
add(std::forward<decltype(args)>(args)...);
};
}
That will create a lambda function that will resolve things with no ambiguity.
I did not forward arguments,
As far as I can see, it's a Visual Studio problem.
c++11 standard (20.8.11)
std::function synopsis
template<class R, class... ArgTypes> class function<R(ArgTypes...)>;
but VisualStudio doesn't have that specialization
clang++ and g++ are perfectly fine with overloading std::functions
prior answers explain why VS doesn't work, but they didn't mention that it's VS' bug

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

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