Warum kann C ++ T bei einem Aufruf von Foo <T> :: Foo (T &&) nicht ableiten?

9

Angesichts der folgenden Vorlagenstruktur:

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

Dies kompiliert und Twird abgeleitet als int:

auto f = Foo(2);

Dies wird jedoch nicht kompiliert: 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&&) {}
    ^
*/

Wird Foo<int&>(x)jedoch akzeptiert.

Aber wenn ich einen scheinbar redundanten benutzerdefinierten Abzugsleitfaden hinzufüge, funktioniert es:

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

Warum kann nicht Twie int&ohne eine benutzerdefinierte Abzugsanleitung abgeleitet werden?

jtbandes
quelle
Diese Frage scheint sich auf Vorlagentypen wieFoo<T<A>>
jtbandes

Antworten:

5

Ich denke, die Verwirrung hier entsteht, weil es eine spezielle Ausnahme für die synthetisierten Abzugsleitfäden in Bezug auf Weiterleitungsreferenzen gibt.

Es ist richtig, dass die Kandidatenfunktion zum Zweck der Ableitung von Klassenvorlagenargumenten, die vom Konstruktor generiert wurde, und die Funktion, die vom benutzerdefinierten Abzugsleitfaden generiert wurde, genau gleich aussehen, dh:

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

Für den vom Konstruktor generierten T&&Wert handelt es sich jedoch um eine einfache r-Wert-Referenz, während es sich im benutzerdefinierten Fall um eine Weiterleitungsreferenz handelt . Dies wird in [temp.deduct.call] / 3 des C ++ 17-Standards angegeben (Entwurf N4659, Hervorhebung von mir):

Eine Weiterleitungsreferenz ist eine rWert-Referenz auf einen nicht qualifizierten cv-Vorlagenparameter , der keinen Vorlagenparameter einer Klassenvorlage darstellt (während der Ableitung von Klassenvorlagenargumenten ([over.match.class.deduct])).

Daher wird der aus dem Klassenkonstruktor synthetisierte Kandidat nicht Twie aus einer Weiterleitungsreferenz (die Tsich als l-Wert-Referenz ableiten könnte , also T&&auch als l-Wert-Referenz) ableiten, sondern nur Tals Nicht-Referenz, also T&&immer eine rWertreferenz.

Nussbaum
quelle
1
Vielen Dank für die klare und prägnante Antwort. Wissen Sie, warum es in den Regeln für die Weiterleitung von Referenzen eine Ausnahme für Klassenvorlagenparameter gibt?
jtbandes
2
@jtbandes Dies scheint sich aufgrund eines Kommentars der US-amerikanischen nationalen Behörde geändert zu haben, siehe Papier p0512r0 . Ich konnte den Kommentar jedoch nicht finden. Ich vermute, wenn Sie einen Konstruktor schreiben, der eine rWert-Referenz verwendet, erwarten Sie normalerweise, dass diese auf die gleiche Weise funktioniert, unabhängig davon, ob Sie eine Foo<int>(...)oder nur angeben Foo(...), was bei Weiterleitungsreferenzen nicht der Fall ist (was Foo<int&>stattdessen abgeleitet werden könnte.
Walnuss
6

Das Problem hierbei ist, dass wir im Konstruktor keinen Typabzug durchführen , da die Klasse als Vorlage verwendet wird . Wir haben immer eine R-Wert-Referenz. Das heißt, der Konstruktor für sieht tatsächlich so aus:TFoo(T&&)Foo

Foo(int&&)

Foo(2)funktioniert, weil 2es ein Wert ist.

Foo(x)nicht, weil xes sich um einen Wert handelt, an den nicht gebunden werden kann int&&. Sie können std::move(x)es auf den entsprechenden Typ umwandeln ( Demo )

Foo<int&>(x)funktioniert einwandfrei, da der Konstruktor Foo(int&)aufgrund von Regeln zum Reduzieren von Referenzen wird; anfangs ist es das, Foo((int&)&&)was Foo(int&)gemäß dem Standard zusammenbricht.

In Bezug auf Ihre "redundante" Abzugsanleitung: Zunächst gibt es eine Standardvorlage für den Abzug von Vorlagen für den Code, die sich im Grunde wie eine Hilfsfunktion verhält:

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

Dies liegt daran, dass der Standard vorschreibt, dass diese (fiktive) Vorlagenmethode dieselben Vorlagenparameter wie die Klasse (Just T) hat, gefolgt von Vorlagenparametern wie der Konstruktor (in diesem Fall keine; der Konstruktor ist nicht mit Vorlagen versehen). Dann sind die Typen der Funktionsparameter dieselben wie im Konstruktor. In unserem Fall Foo<int>sieht der Konstruktor nach der Instanziierung wie Foo(int&&)eine r-Wert-Referenz aus, mit anderen Worten. Daher die Verwendung von add_rvalue_reference_toben.

Offensichtlich funktioniert das nicht.

Wenn Sie Ihren "redundanten" Abzugsleitfaden hinzugefügt haben:

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

Sie erlaubt der Compiler , dass zu unterscheiden, obwohl jede Art von Referenz an Tim Konstruktor ( int&, const int&oder int&&etc.), bestimmt der Typ für die Klasse abgeleitet ohne Bezug zu sein (nur T). Dies liegt daran , dass wir plötzlich sind Typinferenz durchführen.

Jetzt generieren wir eine weitere (fiktive) Hilfsfunktion, die folgendermaßen aussieht:

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

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

(Unsere Anrufe an den Konstruktor für die Zwecke der Klasse Template - Argument Abzug an die Helferfunktion umgeleitet, so Foo(x)wird MakeFoo(x)).

Dies ermöglicht es, einfach U&&zu werden int&und Tzu werdenint

AndyG
quelle
Die zweite Vorlageebene scheint nicht notwendig zu sein. Welchen Wert bietet es? Können Sie einen Link zu einer Dokumentation bereitstellen, die verdeutlicht, warum T && hier immer als Wertreferenz behandelt wird?
jtbandes
1
Aber wenn T noch nicht abgeleitet wurde, was bedeutet "irgendein Typ, der in T konvertierbar ist"?
jtbandes
1
Sie bieten schnell eine Problemumgehung an, aber können Sie sich mehr auf die Erklärung konzentrieren, warum dies nicht funktioniert, wenn Sie es nicht auf irgendeine Weise ändern? Warum funktioniert es nicht so wie es ist? " xist ein Wert, an den man sich nicht binden kann int&&", aber jemand, der nicht versteht, wird verwirrt sein, Foo<int&>(x)was funktionieren könnte, aber nicht automatisch herausgefunden wurde - ich denke, wir alle wollen ein tieferes Verständnis dafür, warum.
Wyck
2
@ Wyck: Ich habe den Beitrag aktualisiert, um mich mehr auf das Warum zu konzentrieren.
AndyG
3
@Wyck Der wahre Grund ist sehr einfach: Der Standard hat die perfekte Weiterleitungssemantik in synthetisierten Abzugsleitfäden speziell deaktiviert , um Überraschungen zu vermeiden.
LF