Il problema qui è che, dal momento che la classe è templato su T, nel costruttore Foo(T&&)stiamo non eseguendo tipo detrazione; Abbiamo sempre un riferimento di valore r. Cioè, il costruttore per Fooeffettivamente assomiglia a questo:
Foo(int&&)
Foo(2)funziona perché 2è un valore.
Foo(x)non perché xè un valore che non può essere associato int&&. Si potrebbe fare std::move(x)per lanciarlo nel tipo appropriato ( demo )
Foo<int&>(x)funziona bene perché il costruttore diventa Foo(int&)dovuto alle regole di crollo di riferimento; inizialmente è quello Foo((int&)&&)che crolla Foo(int&)secondo lo standard.
Per quanto riguarda la tua guida alla detrazione "ridondante": inizialmente esiste una guida alla detrazione del modello predefinita per il codice che sostanzialmente agisce come una funzione di supporto in questo modo:
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);
Questo perché lo standard impone che questo metodo (fittizio) di modello abbia gli stessi parametri del modello della classe (Just T) seguito da tutti i parametri del modello del costruttore (nessuno in questo caso; il costruttore non è modellato). Quindi, i tipi dei parametri della funzione sono gli stessi di quelli nel costruttore. Nel nostro caso, dopo un'istanza Foo<int>, il costruttore assomiglia a Foo(int&&)un riferimento di valore in altre parole. Da qui l'utilizzo di cui add_rvalue_reference_tsopra.
Ovviamente questo non funziona.
Quando hai aggiunto la tua guida alla detrazione "ridondante":
template<typename T>
Foo(T&&) -> Foo<T>;
È permesso il compilatore di distinguere che, nonostante qualsiasi tipo di riferimento collegato a Tnel costruttore ( int&, const int&, o int&&, ecc), si intende il tipo di dedurre per la classe di essere senza di riferimento (solo T). Questo perché improvvisamente ci stiamo eseguendo l'inferenza dei tipi.
Ora generiamo un'altra funzione di supporto (fittizia) che assomiglia a questa:
template<class U>
Foo<U> MakeFoo(U&& u)
{
return Foo<U>(std::forward<U>(u));
}
// ...
auto f = MakeFoo(x);
(Le nostre chiamate al costruttore vengono reindirizzate alla funzione di supporto ai fini della deduzione dell'argomento del modello di classe, così Foo(x)diventa MakeFoo(x)).
Questo permette U&&di diventare int&e Tdiventare semplicementeint