This depends on how you deduce T. For example:
template<class T>
foo1<T> make_foo1( T&& t ) {
return std::forward<T>(t);
}
In this case, the T in foo1<T> is a forwarding reference, and your code won't compile.
std::vector<int> bob{1,2,3};
auto foo = make_foo1(bob);
the above code silently moved from bob into a std::vector<int>& within the constructor to foo1<std::vector<int>&>.
Doing the same with foo2 would work. You'd get a foo2<std::vector<int>&>, and it would hold a reference to bob.
When you write a template, you must consider what it means for the type T to be reference. If your code doesn't support it being a reference, consider static_assert or SFINAE to block that case.
template <typename T>
struct foo1 {
static_assert(!std::is_reference<T>{});
T t_;
foo1(T&& t) :
t_{ std::move(t) }
{
}
};
Now this code generates a reasonable error message.
You might think the existing error message was ok, but it was only ok because we moved into a T.
template <typename T>
struct foo1 {
static_assert(!std::is_reference<T>{});
foo1(T&& t)
{
auto internal_t = std::move(t);
}
};
here only the static_assert ensured that our T&& was actual an rvalue.
But enough with this theoretical list of problems. You have a concrete one.
In the end this is probably want you want:
template <class T> // typename is too many letters
struct foo1 {
static_assert(!std::is_reference<T>{});
T t_;
template<class U,
class dU=std::decay_t<U>, // or remove ref and cv
// SFINAE guard required for all reasonable 1-argument forwarding
// reference constructors:
std::enable_if_t<
!std::is_same<dU, foo1>{} && // does not apply to `foo1` itself
std::is_convertible<U, T> // fail early, instead of in body
,int> = 0
>
foo1(U&& u):
t_(std::forward<U>(u))
{}
// explicitly default special member functions:
foo1()=default;
foo1(foo1 const&)=default;
foo1(foo1 &&)=default;
foo1& operator=(foo1 const&)=default;
foo1& operator=(foo1 &&)=default;
};
or, the simpler case that is just as good in 99/100 cases:
template <class T>
struct foo1 {
static_assert(!std::is_reference<T>{});
T t_;
foo1(T t) :
t_{ std::move(t) }
{}
// default special member functions, just because I never ever
// want to have to memorize the rules that makes them not exist
// or exist based on what other code I have written:
foo1()=default;
foo1(foo1 const&)=default;
foo1(foo1 &&)=default;
foo1& operator=(foo1 const&)=default;
foo1& operator=(foo1 &&)=default;
};
As a general rule, this simpler technique results in exactly 1 move more than the perfect forwarding technique, in exchange for a huge amount less code and complexity. And it permits {} initialization of the T t argument to your constructor, which is nice.