Biorąc pod uwagę następujący szablon struktury:
template<typename T>
struct Foo {
Foo(T&&) {}
};
To się kompiluje i T
można wywnioskować, że int
:
auto f = Foo(2);
Ale to się nie kompiluje: 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&&) {}
^
*/
Jednak Foo<int&>(x)
jest akceptowane.
Ale gdy dodam pozornie zbędny przewodnik dedukcyjny zdefiniowany przez użytkownika, działa:
template<typename T>
Foo(T&&) -> Foo<T>;
Dlaczego nie T
można wywnioskować, jak int&
bez zdefiniowanego przez użytkownika przewodnika odliczeń?
c++
templates
language-lawyer
jtbandes
źródło
źródło
Foo<T<A>>
Odpowiedzi:
Myślę, że powstaje tutaj zamieszanie, ponieważ istnieje szczególny wyjątek dla zsyntetyzowanych przewodników po dedukcji dotyczących przekazywania referencji.
Prawdą jest, że funkcja kandydata do celów dedukcji argumentów szablonu klasy wygenerowana z konstruktora i funkcja wygenerowana na podstawie zdefiniowanego przez użytkownika przewodnika dedukcji wyglądają dokładnie tak samo, tj .:
ale dla generowanego z konstruktora
T&&
jest to proste odwołanie do wartości, podczas gdy w przypadku zdefiniowanym przez użytkownika jest to odwołanie do przekazywania . Jest to określone przez [temp.deduct.call] / 3 standardu C ++ 17 (projekt N4659, podkreśl moje):Dlatego kandydat zsyntetyzowany z konstruktora klasy nie będzie wydedukować,
T
jakby z referencji przekazującej (która mogłaby wydedukowaćT
, żeT&&
jest referencją lvalue, więc jest to również referencja lvalue), ale zamiast tego wydedukuje jedynieT
jako brak referencji, więcT&&
zawsze tak jest odniesienie do wartości.źródło
Foo<int>(...)
tylko”Foo(...)
, czy też „nie” , co nie ma miejsca w przypadku przekazywania referencji (co można wywnioskowaćFoo<int&>
zamiast tegoKwestia jest taka, że, ponieważ klasa jest na matrycy
T
, w konstruktorzeFoo(T&&)
jesteśmy nie wykonującego typu odliczenie; Zawsze mamy referencję wartości r. Oznacza to, że konstruktorFoo
wygląda tak:Foo(2)
działa, ponieważ2
jest prvalue.Foo(x)
nie robi, ponieważx
jest wartością, z którą nie można się powiązaćint&&
. Możesz zrobić,std::move(x)
aby rzucić go na odpowiedni typ ( demo )Foo<int&>(x)
działa dobrze, ponieważ konstruktor staje się zFoo(int&)
powodu reguł zwijania odniesienia; początkowo jest to,Foo((int&)&&)
co spadaFoo(int&)
zgodnie ze standardem.W odniesieniu do „nadmiarowego” przewodnika po dedukcji: Początkowo istnieje domyślny przewodnik po dedukcji szablonów dla kodu, który zasadniczo działa jak funkcja pomocnicza:
Wynika to z faktu, że standard dyktuje, że ta (fikcyjna) metoda szablonu ma te same parametry szablonu co klasa (Just
T
), po której następują dowolne parametry szablonu jako konstruktor (w tym przypadku żaden; konstruktor nie jest szablonowany). Następnie typy parametrów funkcji są takie same jak w konstruktorze. W naszym przypadku po utworzeniu instancjiFoo<int>
konstruktor wygląda jakFoo(int&&)
odwołanie do wartości, innymi słowy. Stąd użycieadd_rvalue_reference_t
powyższego.Oczywiście to nie działa.
Po dodaniu „zbędnego” przewodnika dedukcyjnego:
Pozwoliłeś kompilator do odróżnienia, że mimo wszelkiego rodzaju odniesienie do załączonego
T
w konstruktorze (int&
,const int&
lubint&&
itp), zamierzony rodzaj wywieść dla klasy być bez odniesienia (tylkoT
). To dlatego, że nagle są wykonując typu wnioskowanie.Teraz generujemy kolejną (fikcyjną) funkcję pomocniczą, która wygląda następująco:
(Nasze wywołania do konstruktora są przekierowywane do funkcji pomocnika w celu odliczenia argumentu szablonu klasy, więc
Foo(x)
staje sięMakeFoo(x)
).To pozwala
U&&
stać sięint&
iT
stać się po prostuint
źródło
x
jest wartością, która nie może się wiązaćint&&
”, ale ktoś, kto nie rozumie, będzie zdziwiony, któryFoo<int&>(x)
mógłby zadziałać, ale nie został zorientowany automatycznie - myślę, że wszyscy chcemy głębszego zrozumienia, dlaczego.