Was sind einige Verwendungen von decltype (auto)?

150

In c ++ 14 wird das decltype(auto)Idiom eingeführt.

In der Regel wird verwendet, um autoDeklarationen die Verwendung der decltypeRegeln für den angegebenen Ausdruck zu ermöglichen .

Auf der Suche nach Beispielen für die "gute" Verwendung des Idioms kann ich nur an folgende Dinge denken (von Scott Meyers ), nämlich für den Abzugstypabzug einer Funktion :

template<typename ContainerType, typename IndexType>                // C++14
decltype(auto) grab(ContainerType&& container, IndexType&& index)
{
  authenticateUser();
  return std::forward<ContainerType>(container)[std::forward<IndexType>(index)];
}

Gibt es andere Beispiele, bei denen diese neue Sprachfunktion nützlich ist?

Nikos Athanasiou
quelle
2
In diesem Beitrag wird grundsätzlich empfohlen
user2485710
Ich habe einmal decltype(auto)für etwas Ähnliches verwendet template<class U, V> decltype(auto) first(std::pair<U, V>& p) { return p.first; }, obwohl mir dann klar wurde, dass ich etwas verwenden musste, return (p.first);was überraschenderweise funktioniert (aber IIRC ist dies sogar beabsichtigt).
Dyp
@ user2485710 Ich bin mir nicht sicher, ob es speziell um Optimierung geht. Es besteht eher die Gefahr von Unfällen, wenn decltype(auto)etwas entgegen den Erwartungen in das deklarierte Objekt kopiert / verschoben werden kann.
underscore_d

Antworten:

170

Rückgabetypweiterleitung im generischen Code

Für nicht generischen Code, wie das erste Beispiel, das Sie angegeben haben, können Sie manuell auswählen, um eine Referenz als Rückgabetyp zu erhalten:

auto const& Example(int const& i) 
{ 
    return i; 
}

Im generischen Code möchten Sie jedoch in der Lage sein, einen Rückgabetyp perfekt weiterzuleiten, ohne zu wissen, ob es sich um eine Referenz oder einen Wert handelt. decltype(auto)gibt Ihnen diese Fähigkeit:

template<class Fun, class... Args>
decltype(auto) Example(Fun fun, Args&&... args) 
{ 
    return fun(std::forward<Args>(args)...); 
}

Verzögern des Rückzugs vom Rückgabetyp in rekursiven Vorlagen

In diesen Fragen und Antworten vor einigen Tagen wurde während der Vorlageninstanziierung eine unendliche Rekursion festgestellt, wenn der Rückgabetyp der Vorlage als decltype(iter(Int<i-1>{}))anstelle von angegeben wurde decltype(auto).

template<int i> 
struct Int {};

constexpr auto iter(Int<0>) -> Int<0>;

template<int i>
constexpr auto iter(Int<i>) -> decltype(auto) 
{ return iter(Int<i-1>{}); }

int main() { decltype(iter(Int<10>{})) a; }

decltype(auto)wird hier verwendet, um den Abzug des Rückgabetyps zu verzögern, nachdem sich der Staub der Schabloneninstanziierung gelegt hat.

Andere Verwendungen

Sie können auch decltype(auto)in anderen Kontexten verwenden, z. B. im Entwurf des Standards N3936 auch Staaten

7.1.6.4 Auto Speci fi er [dcl.spec.auto]

1 Die autound decltype(auto)-Typspezifizierer bezeichnen einen Platzhaltertyp, der später ersetzt wird, entweder durch Abzug von einem Initialisierer oder durch explizite Angabe mit einem Trailing-Return-Typ. Dasauto Typspezifizierer wird auch verwendet, um anzuzeigen, dass ein Lambda ein generisches Lambda ist.

2 Der Platzhaltertyp kann mit einem Funktionsdeklarator in der Deklarationsspezifizierer-Sequenz, der Typspezifizierer-Sequenz, der Konvertierungsfunktions-ID oder dem nachfolgenden Rückgabetyp in jedem Kontext angezeigt werden, in dem ein solcher Deklarator gültig ist . Wenn der Funktionsdeklarator einen Trailing-Return-Typ (8.3.5) enthält, gibt dieser den deklarierten Rückgabetyp der Funktion an. Wenn der deklarierte Rückgabetyp der Funktion einen Platzhaltertyp enthält, wird der Rückgabetyp der Funktion gegebenenfalls aus den Rückgabeanweisungen im Hauptteil der Funktion abgeleitet.

Der Entwurf enthält auch dieses Beispiel für die Variableninitialisierung:

int i;
int&& f();
auto x3a = i;                  // decltype(x3a) is int
decltype(auto) x3d = i;        // decltype(x3d) is int
auto x4a = (i);                // decltype(x4a) is int
decltype(auto) x4d = (i);      // decltype(x4d) is int&
auto x5a = f();                // decltype(x5a) is int
decltype(auto) x5d = f();      // decltype(x5d) is int&&
auto x6a = { 1, 2 };           // decltype(x6a) is std::initializer_list<int>
decltype(auto) x6d = { 1, 2 }; // error, { 1, 2 } is not an expression
auto *x7a = &i;                // decltype(x7a) is int*
decltype(auto)*x7d = &i;       // error, declared type is not plain decltype(auto)
TemplateRex
quelle
17
Ist das unterschiedliche Verhalten von (i)vs ieine neue Sache in C ++ 14?
Danvil
14
@ Danvil decltype(expr)unddecltype((expr)) unterscheiden sich bereits in C ++ 11, dies verallgemeinert dieses Verhalten.
TemplateRex
13
Ich habe das gerade gelernt, fühle mich wie eine schreckliche Designentscheidung ... und füge der Syntaxbedeutung von Klammern eine pünktliche Nuance hinzu.
Kahler
Das Beispiel, das diesen Ekel immer hervorruft, ist die einzeilige Datei-zu-Zeichenfolge-Syntax (auch in diesem Link erwähnt). Jeder Teil davon scheint rückständig zu sein. Sie erwarten möglicherweise überhaupt keine Mehrdeutigkeit und entfernen zwanghaft redundante Klammern aus einer Stichprobe. Sie würden erwarten, dass sich die Unklarheit durch den Prozess der Beseitigung gemäß SFINAE auflöst, aber andere potenzielle Kandidaten als die Erklärung werden im Voraus beseitigt (SF ist AE). und frustriert könnten Sie weitermachen, sobald sich herausstellt, dass die willkürlichen Parens Mehrdeutigkeiten lösen, aber sie führen sie ein. Am ärgerlichsten für CS101-Professoren, wie ich mir vorstellen kann.
John P
@TemplateRex: Über das Verzögern der Auflösung des Rückgabetyps in der referenzierten Frage: Soweit ich sehe, in der spezifischen Szenario ein einfacher autodie Arbeit genauso gut erledigt, da das Ergebnis ohnehin nach Wert zurückgegeben wird ... Oder habe ich es verpasst etwas?
Aconcagua
36

Zitate von hier :

  • decltype(auto)Dies ist in erster Linie nützlich, um den Rückgabetyp von Weiterleitungsfunktionen und ähnlichen Wrappern abzuleiten , bei denen der Typ einen von Ihnen aufgerufenen Ausdruck genau „verfolgen“ soll.

  • Zum Beispiel mit den folgenden Funktionen:


   string  lookup1();
   string& lookup2();

  • In C ++ 11 könnten wir die folgenden Wrapper-Funktionen schreiben, die daran denken, die Referenz des Rückgabetyps beizubehalten:

   string  look_up_a_string_1() { return lookup1(); }
   string& look_up_a_string_2() { return lookup2(); }

  • In C ++ 14 können wir das automatisieren:

   decltype(auto) look_up_a_string_1() { return lookup1(); }
   decltype(auto) look_up_a_string_2() { return lookup2(); }

  • Es decltype(auto)ist jedoch nicht beabsichtigt, darüber hinaus ein weit verbreitetes Merkmal zu sein.

  • Insbesondere, obwohl es zum Deklarieren lokaler Variablen verwendet werden kann, ist dies wahrscheinlich nur ein Gegenmuster, da die Referenz einer lokalen Variablen nicht vom Initialisierungsausdruck abhängen sollte.

  • Es hängt auch davon ab, wie Sie die return-Anweisung schreiben.

  • Die beiden folgenden Funktionen haben beispielsweise unterschiedliche Rückgabetypen:


   decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; }
   decltype(auto) look_up_a_string_2() { auto str = lookup2(); return(str); }

  • Die erste gibt zurück string, die zweite gibt zurück string&, was eine Referenz auf die lokale Variable ist str.

Aus dem Vorschlag können Sie weitere Verwendungszwecke ersehen.

101010
quelle
3
Warum nicht einfach autofür die Rücksendung verwenden?
BЈовић
@ BЈовић könnte auch mit einem allgemeinen Abzug vom Rückgabetyp (dh autoRückgabe) arbeiten, aber das OP hat speziell nach Verwendungen von gefragt decltype(auto).
101010
3
Die Frage ist jedoch immer noch relevant. Was wäre der Rückgabetyp auto lookup_a_string() { ... } ? Ist es immer ein Nichtreferenztyp? Und muss daher auto lookup_a_string() ->decltype(auto) { ... }erzwungen werden, dass Referenzen (in einigen Fällen) zurückgegeben werden können?
Aaron McDaid
@AaronMcDaid Deductible autowird als Übergabe-Wert-Vorlage definiert, daher kann es sich nicht um eine Referenz handeln. Bitte warten, autokann natürlich alles sein, einschließlich einer Referenz.
Neugieriger
4
Ein weiteres erwähnenswertes Beispiel ist die Rückgabe eines Elements von a std::vector. Sagen Sie, Sie haben template<typename T> struct S { auto & operator[](std::size_t i) { return v[i]; } std::vector<T> v; }. Dann S<bool>::operator[]wird aufgrund der Spezialisierung von eine baumelnde Referenz zurückgegeben std::vector<bool>. Durch Ändern des Rückgabetyps, decltype(auto)um dieses Problem zu umgehen.
Xoph