Dlaczego C ++ nie może wywnioskować T w rozmowie z Foo <T> :: Foo (T&&)?

9

Biorąc pod uwagę następujący szablon struktury:

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

To się kompiluje i Tmoż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 Tmożna wywnioskować, jak int&bez zdefiniowanego przez użytkownika przewodnika odliczeń?

jtbandes
źródło
To pytanie wydaje się dotyczyć typów szablonów, takich jakFoo<T<A>>
jtbandes

Odpowiedzi:

5

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 .:

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

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

Odniesienia spedycja jest odniesienie RValue do szablonu parametr CV bez zastrzeżeń , że nie reprezentuje parametr szablonu szablon klasy (klasa szablonu podczas odliczenia argument ([over.match.class.deduct])).

Dlatego kandydat zsyntetyzowany z konstruktora klasy nie będzie wydedukować, Tjakby z referencji przekazującej (która mogłaby wydedukować T, że T&&jest referencją lvalue, więc jest to również referencja lvalue), ale zamiast tego wydedukuje jedynie Tjako brak referencji, więc T&&zawsze tak jest odniesienie do wartości.

orzech włoski
źródło
1
Dziękuję za jasną i zwięzłą odpowiedź. Czy wiesz, dlaczego istnieje wyjątek dla parametrów szablonu klasy w regułach przekazywania referencji?
jtbandes
2
@jtbandes Wygląda na to, że zostało to zmienione w wyniku komentarza krajowego organu USA, patrz artykuł p0512r0 . Nie mogłem jednak znaleźć komentarza. Moim zdaniem uzasadnienie jest takie, że jeśli napiszesz konstruktora, biorąc odwołanie do wartości, zwykle oczekujesz, że będzie działał w ten sam sposób, niezależnie od tego, czy podasz wartość „ Foo<int>(...)tylko” Foo(...), czy też „nie” , co nie ma miejsca w przypadku przekazywania referencji (co można wywnioskować Foo<int&>zamiast tego
orzech włoski
6

Kwestia jest taka, że, ponieważ klasa jest na matrycy T, w konstruktorze Foo(T&&)jesteśmy nie wykonującego typu odliczenie; Zawsze mamy referencję wartości r. Oznacza to, że konstruktor Foowygląda tak:

Foo(int&&)

Foo(2)działa, ponieważ 2jest prvalue.

Foo(x)nie robi, ponieważ xjest 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ę z Foo(int&)powodu reguł zwijania odniesienia; początkowo jest to, Foo((int&)&&)co spada Foo(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:

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

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 instancji Foo<int>konstruktor wygląda jak Foo(int&&)odwołanie do wartości, innymi słowy. Stąd użycie add_rvalue_reference_tpowyższego.

Oczywiście to nie działa.

Po dodaniu „zbędnego” przewodnika dedukcyjnego:

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

Pozwoliłeś kompilator do odróżnienia, że mimo wszelkiego rodzaju odniesienie do załączonego Tw konstruktorze ( int&, const int&lubint&& itp), zamierzony rodzaj wywieść dla klasy być bez odniesienia (tylko T). To dlatego, że nagle wykonując typu wnioskowanie.

Teraz generujemy kolejną (fikcyjną) funkcję pomocniczą, która wygląda następująco:

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

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

(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&i Tstać się po prostuint

AndyG
źródło
Drugi poziom szablonów nie wydaje się konieczny; jaką to zapewnia? Czy możesz podać link do dokumentacji wyjaśniającej, dlaczego T&& jest tutaj zawsze traktowane jako odniesienie do wartości?
jtbandes
1
Ale jeśli T nie zostało jeszcze wydedukowane, co oznacza „dowolny typ zamienny na T”?
jtbandes
1
Szybko zaproponujesz obejście, ale czy możesz skupić się bardziej na wyjaśnieniu przyczyny, dla której nie działa, chyba że w jakiś sposób zmodyfikujesz go? Dlaczego to nie działa tak jak jest? „ xjest wartością, która nie może się wiązać int&&”, ale ktoś, kto nie rozumie, będzie zdziwiony, który Foo<int&>(x)mógłby zadziałać, ale nie został zorientowany automatycznie - myślę, że wszyscy chcemy głębszego zrozumienia, dlaczego.
Wyck
2
@ Wyck: Zaktualizowałem post, aby bardziej skupić się na tym, dlaczego.
AndyG
3
@Wyck Prawdziwy powód jest bardzo prosty: standard specjalnie wyłączył idealną semantykę przekazywania w syntetycznych przewodnikach dedukcyjnych, aby zapobiec niespodziankom.
LF