Angesichts der folgenden Vorlagenstruktur:
template<typename T>
struct Foo {
Foo(T&&) {}
};
Dies kompiliert und T
wird 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 T
wie int&
ohne eine benutzerdefinierte Abzugsanleitung abgeleitet werden?
c++
templates
language-lawyer
jtbandes
quelle
quelle
Foo<T<A>>
Antworten:
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:
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):Daher wird der aus dem Klassenkonstruktor synthetisierte Kandidat nicht
T
wie aus einer Weiterleitungsreferenz (dieT
sich als l-Wert-Referenz ableiten könnte , alsoT&&
auch als l-Wert-Referenz) ableiten, sondern nurT
als Nicht-Referenz, alsoT&&
immer eine rWertreferenz.quelle
Foo<int>(...)
oder nur angebenFoo(...)
, was bei Weiterleitungsreferenzen nicht der Fall ist (wasFoo<int&>
stattdessen abgeleitet werden könnte.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:
T
Foo(T&&)
Foo
Foo(2)
funktioniert, weil2
es ein Wert ist.Foo(x)
nicht, weilx
es sich um einen Wert handelt, an den nicht gebunden werden kannint&&
. Sie könnenstd::move(x)
es auf den entsprechenden Typ umwandeln ( Demo )Foo<int&>(x)
funktioniert einwandfrei, da der KonstruktorFoo(int&)
aufgrund von Regeln zum Reduzieren von Referenzen wird; anfangs ist es das,Foo((int&)&&)
wasFoo(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:
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 FallFoo<int>
sieht der Konstruktor nach der Instanziierung wieFoo(int&&)
eine r-Wert-Referenz aus, mit anderen Worten. Daher die Verwendung vonadd_rvalue_reference_t
oben.Offensichtlich funktioniert das nicht.
Wenn Sie Ihren "redundanten" Abzugsleitfaden hinzugefügt haben:
Sie erlaubt der Compiler , dass zu unterscheiden, obwohl jede Art von Referenz an
T
im Konstruktor (int&
,const int&
oderint&&
etc.), bestimmt der Typ für die Klasse abgeleitet ohne Bezug zu sein (nurT
). Dies liegt daran , dass wir plötzlich sind Typinferenz durchführen.Jetzt generieren wir eine weitere (fiktive) Hilfsfunktion, die folgendermaßen aussieht:
(Unsere Anrufe an den Konstruktor für die Zwecke der Klasse Template - Argument Abzug an die Helferfunktion umgeleitet, so
Foo(x)
wirdMakeFoo(x)
).Dies ermöglicht es, einfach
U&&
zu werdenint&
undT
zu werdenint
quelle
x
ist ein Wert, an den man sich nicht binden kannint&&
", 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.