The issue here is that, since the class is templated on T
, in the constructor Foo(T&&)
we are not performing type deduction; We always have an r-value reference. That is, the constructor for Foo
actually looks like this:
Foo(int&&)
Foo(2)
works because 2
is a prvalue.
Foo(x)
does not because x
is an lvalue that cannot bind to int&&
. You could do std::move(x)
to cast it to the appropriate type (demo)
Foo<int&>(x)
works just fine because the constructor becomes Foo(int&)
due to reference collapsing rules; initially it's Foo((int&)&&)
which collapses to Foo(int&)
per the standard.
In regards to your "redundant" deduction guide:
Initially there is a default template deduction guide for the code that basically acts like a helper function like so:
template<typename T>
struct Foo {
Foo(T&&) {}
};
template<typename T>
Foo<T> MakeFoo(std::add_rvalue_reference_t<T> value)
{
return Foo<T>(std::move(value));
}
//...
auto f = MakeFoo(x);
This is because the standard dictates that this (fictional) template method has the same template parameters as the class (Just T
) followed by any template parameters as the constructor (none in this case; the constructor is not templated). Then, the types of the function parameters are the same as those in the constructor. In our case, after instantiating Foo<int>
, the constructor looks like Foo(int&&)
, an rvalue-reference in other words. Hence the usage of add_rvalue_reference_t
above.
Obviously this doesn't work.
When you added your "redundant" deduction guide:
template<typename T>
Foo(T&&) -> Foo<T>;
You allowed the compiler to distinguish that, despite any kind of reference attached to T
in the constructor (int&
, const int&
, or int&&
etc.), you intended the type inferred for the class to be without reference (just T
). This is because we suddenly are performing type inference.
Now we generate another (fictional) helper function that looks like this:
template<class U>
Foo<U> MakeFoo(U&& u)
{
return Foo<U>(std::forward<U>(u));
}
// ...
auto f = MakeFoo(x);
(Our calls to the constructor are redirected to the helper function for the purposes of class template argument deduction, so Foo(x)
becomes MakeFoo(x)
).
This allows U&&
to become int&
and T
to become simply int