Por que a dedução de argumento de modelo é desativada com std :: forward?

No VS2010 std :: forward é definido como tal:

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

identity parece ser usada apenas para desabilitar a dedução do argumento de modelo. Qual é o propósito de desabilitá-lo intencionalmente neste caso?

Se você passar uma referência de valor para um object do tipo X para uma function de modelo que toma o tipo T&& como seu parâmetro, dedução de argumento de modelo deduz T para ser X Portanto, o parâmetro tem tipo X&& . Se o argumento da function for um lvalue ou const lvalue, o compilador deduzirá que seu tipo seja uma referência de lvalue ou uma referência de const lvalue desse tipo.

Se std::forward usou dedução de argumento de modelo:

Como os objects with names are lvalues a única vez que std::forward seria convertido corretamente para T&& seria quando o argumento de input era um rvalue sem nome (como 7 ou func() ). No caso de encaminhamento perfeito, o arg você passa para std::forward é um lvalue porque tem um nome. std::forward seria deduzido como referência lvalue ou referência const lvalue. As regras de recolhimento de referência causariam o T&& em static_cast(arg) em std :: forward para sempre resolver como uma referência de lvalue ou referência de valor de const.

Exemplo:

 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)); } 

Porque std::forward(foo) não é útil. A única coisa que pode fazer é um não-op, ou seja, perfeitamente encaminhar seu argumento e agir como uma function identitária. A alternativa seria o mesmo que std::move , mas temos isso. Em outras palavras, supondo que fosse possível, em

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

std::forward(t) é semanticamente equivalente a t . Por outro lado, std::forward(t) não é um no-op no caso geral.

Então, ao proibir o std::forward(t) ele ajuda a capturar erros do programador e não perdemos nada, já que qualquer uso possível do std::forward(t) é substituído por t .


Eu acho que você entenderia melhor as coisas se nos focarmos no que exatamente o std::forward(t) faz , ao invés do que o std::forward(t) faria (já que é um no-op desinteressante). Vamos tentar escrever um modelo de function não-operacional que transmita perfeitamente seu argumento. Além disso, para maior clareza, esse modelo usará U como seu parâmetro de modelo. Qualquer uso de T irá se referir ao parâmetro template do próprio std::forward .

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

Essa primeira tentativa ingênua não é válida. Se chamarmos f(0) então U é deduzido como int . Isso significa que o tipo de retorno é int&& e não podemos vincular essa referência rvalue da expressão u , que é um lvalue (é o nome de uma variável local). Se então tentarmos:

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

então int i = 0; f(i); int i = 0; f(i); falha. Desta vez, U é deduzido como int& (regras de colapso de referência garantem que int& && recolhe para int& ), portanto o tipo de retorno é int& , e desta vez não podemos vincular tal referência lvalue da expressão std::move(u) que é um valor x.

No contexto de uma function de encaminhamento perfeito como f , às vezes queremos nos mover, mas outras vezes não. A regra para saber se devemos nos mover depende de U : se não for um tipo de referência lvalue, significa que f foi passado por um rvalue. Se for um tipo de referência lvalue ( U& ), significa que f foi passado um lvalue. Portanto, em std::forward(u) , U é um parâmetro necessário para fazer a coisa certa. Sem isso, não há informação suficiente. Este U não é o mesmo tipo do que T seria deduzido (dentro de std::forward ) no caso geral.

Edit: O seguinte demonstra a raiz da minha confusão. Por favor, explique por que ele chama o somefunc # 1 em vez do somefunc # 2.

Em ForwardingFunc x é sempre considerado um lvalue em cada uso:

 somefunc(forward(x)); 

Objetos com nomes são sempre lvalues.

Esse é um recurso importante de segurança. Se você tem um nome para isso, você não quer se mover implicitamente a partir dele: seria muito fácil sair dele duas vezes:

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

Na chamada para forward , T deduz como int& porque o argumento x é um lvalue. Então você está chamando essa especialização:

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

Porque T deduz como int& e int& && recolhe de volta para int& .

Como forward(x) retorna um int& , a chamada para somefunc combina perfeitamente com a sobrecarga # 1.