Dado o seguinte modelo struct:
template<typename T>
struct Foo {
Foo(T&&) {}
};
Isso compila e T
deduz-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 T
ser deduzido int&
sem um guia de dedução definido pelo usuário?
c++
templates
language-lawyer
jtbandes
fonte
fonte
Foo<T<A>>
Respostas:
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:
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):Portanto, o candidato sintetizado a partir do construtor da classe não deduzirá
T
como se fosse uma referência de encaminhamento (que poderia deduzirT
ser uma referência lvalue, de modo queT&&
também é uma referência lvalue), mas apenas deduziráT
como não referência, portanto,T&&
sempre uma referência rvalue.fonte
Foo<int>(...)
ou apenasFoo(...)
, o que não é o caso das referências de encaminhamento (que podem ser deduzidasFoo<int&>
.A questão aqui é que, uma vez que a classe é modelada em
T
, no construtorFoo(T&&)
estamos não realizando tipo dedução; Sempre temos uma referência de valor-r. Ou seja, o construtor paraFoo
se parece com isso:Foo(2)
funciona porque2
é um pré-valor.Foo(x)
não porquex
é um valor l que não pode ser vinculadoint&&
. Você pode fazer issostd::move(x)
para transmiti-lo ao tipo apropriado ( demo )Foo<int&>(x)
funciona muito bem porque o construtor se tornaFoo(int&)
devido a regras de recolhimento de referência; inicialmente é oFoo((int&)&&)
que cai deFoo(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:
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 instanciarFoo<int>
, o construtor se parece comFoo(int&&)
uma referência de valor em outras palavras. Daí o usoadd_rvalue_reference_t
acima.Obviamente, isso não funciona.
Quando você adicionou seu guia de dedução "redundante":
Você permitiu que o compilador para distinguir que, apesar de qualquer tipo de referência ligado a
T
no construtor (int&
,const int&
ouint&&
etc.), que pretendia o tipo inferido para a classe para ser sem referência (apenasT
). Isso ocorre porque de repente somos realizando inferência de tipo.Agora, geramos outra função auxiliar (fictícia) que se parece com isso:
(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-seint&
eT
tornar-se simplesmenteint
fonte
x
é um valor que não pode ser vinculado aint&&
", mas alguém que não entende ficará intrigado com o queFoo<int&>(x)
poderia funcionar, mas não foi descoberto automaticamente - acho que todos queremos uma compreensão mais profunda do porquê.