Perché la deduzione degli argomenti del template è disabilitata con std :: forward?

In VS2010 std :: forward è definito come tale:

template inline _Ty&& forward(typename identity::type& _Arg) { // forward _Arg, given explicitly specified type parameter return ((_Ty&&)_Arg); } 

identity sembra essere utilizzata esclusivamente per disabilitare la deduzione degli argomenti del modello. Qual è il punto di disabilitarlo intenzionalmente in questo caso?

Se si passa un riferimento di rvalue a un object di tipo X a una funzione di modello che prende come parametro il tipo T&& , deduzione argomento modello deduce che T è X Pertanto, il parametro ha tipo X&& . Se l’argomento della funzione è un lvalue o const lvalue, il compilatore deduce il suo tipo come riferimento di lvalue o riferimento a const -value di quel tipo.

Se std::forward deduzione argomento template utilizzata:

Dato che gli objects with names are lvalues l’unica volta che std::forward verrebbe castato correttamente in T&& sarebbe quando l’argomento di input era un valore non denominato (come 7 o func() ). Nel caso di inoltro perfetto l’argomento che passi a std::forward è un lvalue perché ha un nome. std::forward sarebbe dedotto come riferimento di lvalue o come riferimento di const lvalue. Le regole di compressione dei riferimenti farebbero sì che il T&& in static_cast(arg) in std :: forward si risolva sempre come riferimento lvalue o riferimento a lvalue const.

Esempio:

 template T&& forward_with_deduction(T&& obj) { return static_cast(obj); } void test(int&){} void test(const int&){} void test(int&&){} template void perfect_forwarder(T&& obj) { test(forward_with_deduction(obj)); } int main() { int x; const int& y(x); int&& z = std::move(x); test(forward_with_deduction(7)); // 7 is an int&&, correctly calls test(int&&) test(forward_with_deduction(z)); // z is treated as an int&, calls test(int&) // All the below call test(int&) or test(const int&) because in perfect_forwarder 'obj' is treated as // an int& or const int& (because it is named) so T in forward_with_deduction is deduced as int& // or const int&. The T&& in static_cast(obj) then collapses to int& or const int& - which is not what // we want in the bottom two cases. perfect_forwarder(x); perfect_forwarder(y); perfect_forwarder(std::move(x)); perfect_forwarder(std::move(y)); } 

Perché std::forward(foo) non è utile. L’unica cosa che può fare è un no-op, cioè inoltrare perfettamente la sua argomentazione e agire come una funzione di identity framework. L’alternativa sarebbe che è lo stesso di std::move , ma lo abbiamo già . In altre parole, supponendo che fosse ansible, in

 template void f(T&& t) { std::forward(t); } 

std::forward(t) è semanticamente equivalente a t . D’altra parte, std::forward(t) non è un no-op nel caso generale.

Quindi proibendo std::forward(t) aiuta a catturare gli errori del programmatore e non perdiamo nulla dal momento che qualsiasi ansible uso di std::forward(t) è banalmente sostituito da t .


Penso che capiresti meglio le cose se ci concentreremo su cosa esattamente std::forward(t) fa , piuttosto che cosa std::forward(t) farebbe (dato che è un no-op non interessante). Proviamo a scrivere un modello di funzione no-op che inoltra perfettamente la sua argomentazione. Inoltre, per chiarezza, questo modello userà U come parametro del modello. Qualsiasi utilizzo di T farà riferimento al parametro template di std::forward stesso.

 template U&& f(U&& u) { return u; } 

Questo primo tentativo ingenuo non è abbastanza valido. Se chiamiamo f(0) allora U viene dedotto come int . Ciò significa che il tipo restituito è int&& e non possiamo associare un riferimento di tale valore dall’espressione u , che è un lvalue (è il nome di una variabile locale). Se tentiamo allora:

 template U&& f(U&& u) { return std::move(u); } 

allora int i = 0; f(i); int i = 0; f(i); non riesce. Questa volta, U è dedotta come int& (le regole di compressione di riferimento garantiscono che int& && comprime in int& ), quindi il tipo restituito è int& , e questa volta non possiamo associare un riferimento di tale valore dall’espressione std::move(u) che è un xvalue.

Nel contesto di una funzione di inoltro perfetto come f , a volte vogliamo spostarci, ma altre volte no. La regola per sapere se dovremmo spostarci dipende da U : se non è un tipo di riferimento lvalue, significa che f stato passato un valore. Se è un tipo di riferimento di lvalue ( U& ), significa che f stato passato un lvalue. Quindi in std::forward(u) , U è un parametro necessario per fare la cosa giusta. Senza di esso, non ci sono abbastanza informazioni. Questo U non è lo stesso tipo di quello che T verrebbe dedotto (all’interno di std::forward ) nel caso generale.

Modifica: Quanto segue dimostra la radice della mia confusione. Per favore spiega perché chiama somefunc # 1 invece di somefunc # 2.

In ForwardingFunc x è sempre considerato un lvalue per ogni utilizzo:

 somefunc(forward(x)); 

Gli oggetti con i nomi sono sempre lvalue.

Questa è una caratteristica chiave di sicurezza. Se hai un nome per questo, non vuoi spostarlo implicitamente da esso: sarebbe troppo facile spostarlo due volte:

 foo(x); // no implicit move bar(x); // else this would probably not do what you intend 

Nella chiamata da forward , T deduce come int& perché l’argomento x è un lvalue. Quindi stai chiamando questa specializzazione:

 template<> int& forward(int& x) { return static_cast(x); } 

Poiché T deduce int& e int& && riduci indietro a int& .

Poiché forward(x) restituisce un int& , la chiamata a somefunc corrisponde perfettamente al sovraccarico # 1.