Por que o C ++ não pode deduzir T em uma chamada para Foo <T> :: Foo (T&&)?

9

Dado o seguinte modelo struct:

template<typename T>
struct Foo {
    Foo(T&&) {}
};

Isso compila e Tdeduz-se a ser int:

auto f = Foo(2);

Mas isso não compila: https://godbolt.org/z/hAA9TE

int x = 2;
auto f = Foo(x);

/*
<source>:12:15: error: no viable constructor or deduction guide for deduction of template arguments of 'Foo'
    auto f = Foo(x);
             ^

<source>:7:5: note: candidate function [with T = int] not viable: no known conversion from 'int' to 'int &&' for 1st argument
    Foo(T&&) {}
    ^
*/

No entanto, Foo<int&>(x)é aceito.

Mas quando adiciono um guia de dedução definido pelo usuário aparentemente redundante, ele funciona:

template<typename T>
Foo(T&&) -> Foo<T>;

Por que não pode Tser deduzido int&sem um guia de dedução definido pelo usuário?

jtbandes
fonte
Essa pergunta parece ser sobre tipos de modelo comoFoo<T<A>>
jtbandes

Respostas:

5

Eu acho que a confusão aqui surge porque há uma exceção específica para os guias de dedução sintetizados em relação às referências de encaminhamento.

É verdade que a função candidata com a finalidade de dedução de argumento do modelo de classe gerada a partir do construtor e a função gerada a partir do guia de dedução definido pelo usuário são exatamente iguais, ou seja:

template<typename T>
auto f(T&&) -> Foo<T>;

mas para o gerado a partir do construtor, T&&é uma simples referência de valor, enquanto é uma referência de encaminhamento no caso definido pelo usuário. Isso é especificado por [temp.deduct.call] / 3 do padrão C ++ 17 (rascunho N4659, enfatize o meu):

Uma referência de encaminhamento é uma referência rvalue a um parâmetro de modelo não qualificado de cv que não representa um parâmetro de modelo de um modelo de classe (durante a dedução do argumento do modelo de classe ([over.match.class.deduct])).

Portanto, o candidato sintetizado a partir do construtor da classe não deduzirá Tcomo se fosse uma referência de encaminhamento (que poderia deduzir Tser uma referência lvalue, de modo que T&&também é uma referência lvalue), mas apenas deduzirá Tcomo não referência, portanto, T&&sempre uma referência rvalue.

noz
fonte
11
Obrigado pela resposta clara e concisa. Você sabe por que há uma exceção para os parâmetros do modelo de classe nas regras para encaminhamento de referências?
jtbandes
2
@jtbandes Isso parece ter sido alterado como resultado de um comentário do órgão nacional dos EUA, consulte o documento p0512r0 . Não consegui encontrar o comentário. Meu palpite para a lógica é que, se você escrever um construtor usando uma referência rvalue, normalmente espera que ele funcione da mesma maneira, se você especificar um Foo<int>(...)ou apenas Foo(...), o que não é o caso das referências de encaminhamento (que podem ser deduzidas Foo<int&>.
walnut
6

A questão aqui é que, uma vez que a classe é modelada em T, no construtor Foo(T&&)estamos não realizando tipo dedução; Sempre temos uma referência de valor-r. Ou seja, o construtor para Foose parece com isso:

Foo(int&&)

Foo(2)funciona porque 2é um pré-valor.

Foo(x)não porque xé um valor l que não pode ser vinculado int&&. Você pode fazer isso std::move(x)para transmiti-lo ao tipo apropriado ( demo )

Foo<int&>(x)funciona muito bem porque o construtor se torna Foo(int&)devido a regras de recolhimento de referência; inicialmente é o Foo((int&)&&)que cai de Foo(int&)acordo com o padrão.

Em relação ao seu guia de dedução "redundante": Inicialmente, há um guia de dedução de modelo padrão para o código que basicamente atua como uma função auxiliar da seguinte forma:

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

Isso ocorre porque o padrão determina que esse método de modelo (fictício) possua os mesmos parâmetros de modelo que a classe (Just T), seguidos por quaisquer parâmetros de modelo que o construtor (nenhum neste caso; o construtor não tem modelo). Em seguida, os tipos dos parâmetros de função são os mesmos do construtor. No nosso caso, após instanciar Foo<int>, o construtor se parece com Foo(int&&)uma referência de valor em outras palavras. Daí o uso add_rvalue_reference_tacima.

Obviamente, isso não funciona.

Quando você adicionou seu guia de dedução "redundante":

template<typename T>
Foo(T&&) -> Foo<T>;

Você permitiu que o compilador para distinguir que, apesar de qualquer tipo de referência ligado a Tno construtor ( int&, const int&ou int&&etc.), que pretendia o tipo inferido para a classe para ser sem referência (apenas T). Isso ocorre porque de repente somos realizando inferência de tipo.

Agora, geramos outra função auxiliar (fictícia) que se parece com isso:

template<class U>
Foo<U> MakeFoo(U&& u)
{
   return Foo<U>(std::forward<U>(u));
}

// ...
auto f = MakeFoo(x);

(Nossas chamadas para o construtor são redirecionadas para a função auxiliar para fins de dedução do argumento do modelo de classe, Foo(x)tornando - seMakeFoo(x) ).

Isso permite U&&tornar-se int&e Ttornar-se simplesmenteint

AndyG
fonte
O segundo nível de modelagem não parece necessário; que valor isso fornece? Você pode fornecer um link para alguma documentação que esclarece por que os T&& são sempre tratados como uma referência de valor aqui?
jtbandes
11
Mas se T ainda não foi deduzido, o que significa "qualquer tipo conversível em T"?
jtbandes
11
Você é rápido em oferecer uma solução alternativa, mas pode se concentrar mais na explicação pelo motivo pelo qual ela não funciona, a menos que você a modifique de alguma forma? Por que não funciona como está? " xé um valor que não pode ser vinculado a int&&", mas alguém que não entende ficará intrigado com o que Foo<int&>(x)poderia funcionar, mas não foi descoberto automaticamente - acho que todos queremos uma compreensão mais profunda do porquê.
Wyck
2
@ Wyck: Eu atualizei o post para focar mais no porquê.
AndyG 01/03
3
@ Wyck A verdadeira razão é muito simples: o padrão desativou especificamente a semântica de encaminhamento perfeito nos guias de dedução sintetizados para evitar surpresas.
LF