c++ - Gibt es realistische Anwendungsfälle für "decltype(auto)"-Variablen?




c++14 type-deduction (2)

Sowohl aus meiner persönlichen Erfahrung als auch aus der Beratung von Antworten auf Fragen wie Was sind einige Verwendungszwecke von decltype (auto)? Ich kann viele nützliche Anwendungsfälle für decltype(auto) als Platzhalter für Funktionsrückgabetypen finden .

Es fällt mir jedoch schwer, an einen gültigen (dh nützlichen, realistischen, nützlichen) Anwendungsfall für decltype(auto) Variablen zu denken. Die einzige Möglichkeit, die mir in den Sinn kommt, besteht darin, das Ergebnis einer Funktion zu speichern, die decltype(auto) für eine spätere Weitergabe decltype(auto) , aber auto&& könnte auch dort verwendet werden und wäre einfacher.

Ich habe sogar alle meine Projekte und Experimente durchsucht, und die 391 Vorkommen von decltype(auto) sind alle Platzhalter für Rückgabetypen.

decltype(auto) es also realistische Anwendungsfälle für decltype(auto) Variablen decltype(auto) Variablen)? Oder ist diese Funktion nur nützlich, wenn sie als Platzhalter für Rückgabetypen verwendet wird?

Wie definieren Sie "realistisch"?

Ich suche nach einem Anwendungsfall, der Wert liefert (dh nicht nur ein Beispiel für die Funktionsweise der Funktion), bei dem decltype(auto) die perfekte Wahl ist, verglichen mit Alternativen wie auto&& oder dem decltype(auto) auf die Deklaration einer Variablen.

Die Problemdomäne spielt keine Rolle, es könnte sich um einen obskuren Fall einer Metaprogrammierecke oder ein arkanes Konstrukt für funktionale Programmierung handeln. Das Beispiel müsste mich jedoch dazu bringen, zu sagen: "Hey, das ist schlau / schön!" und die Verwendung eines anderen Merkmals, um den gleichen Effekt zu erzielen, würde mehr Boilerplate erfordern oder irgendeine Art von Nachteil haben.


Im Wesentlichen ist der Fall für Variablen für Funktionen derselbe. Die Idee ist, dass wir das Ergebnis eines Funktionsaufrufs mit einer decltype(auto) speichern:

decltype(auto) result = /* function invocation */;

Dann ist das result

  • ein Nichtreferenztyp, wenn das Ergebnis ein Wert ist,

  • ein (möglicherweise cv-qualifizierter) lWert-Referenztyp, wenn das Ergebnis ein lWert ist, oder

  • Ein rWert-Referenztyp, wenn das Ergebnis ein xWert ist.

Jetzt brauchen wir eine neue Version von forward , um zwischen dem Fall prvalue und dem Fall xvalue zu unterscheiden: (Der Name forward wird vermieden, um ADL-Probleme zu vermeiden.)

template <typename T>
T my_forward(std::remove_reference_t<T>& arg)
{
    return std::forward<T>(arg);
}

Und dann benutzen

my_forward<decltype(result)>(result)

Im Gegensatz zu std::forward wird diese Funktion zum Weiterleiten decltype(auto) Variablen vom Typ decltype(auto) . Daher wird ein Referenztyp nicht unbedingt zurückgegeben, sondern mit decltype(variable) aufgerufen, wobei es sich um T , T& oder T&& , damit zwischen lWerten, xWerten und prWerten unterschieden werden kann. Also, wenn das result ist

  • ein Nichtreferenztyp, dann wird die zweite Überladung mit einem Nichtreferenz- T aufgerufen, und ein Nichtreferenztyp wird zurückgegeben, was zu einem Wert führt;

  • ein lvalue-Referenztyp, dann wird die erste Überladung mit einem T& aufgerufen und T& zurückgegeben, was zu einem lvalue führt;

  • ein rvalue-Referenztyp, dann wird die zweite Überladung mit einem T&& aufgerufen und T&& wird zurückgegeben, was zu einem xvalue führt.

Hier ist ein Beispiel. Stellen Sie sich vor, Sie möchten std::invoke und etwas in das Protokoll schreiben: (das Beispiel dient nur zur Veranschaulichung)

template <typename F, typename... Args>
decltype(auto) my_invoke(F&& f, Args&&... args)
{
    decltype(auto) result = std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
    my_log("invoke", result); // for illustration only
    return my_forward<decltype(result)>(result);
}

Nun, wenn der Aufrufausdruck ist

  • Ein Wert, dann ist result ein Nichtreferenztyp und die Funktion gibt einen Nichtreferenztyp zurück.

  • ein nicht konstanter Wert, dann ist result eine nicht konstanter Wert-Referenz, und die Funktion gibt einen nicht konstanter Wert-Referenz-Typ zurück;

  • Ein konstanter Wert. Das result ist eine konstante Wertreferenz. Die Funktion gibt einen konstanten Wertreferenztyp zurück.

  • Ein x-Wert, dann ist result ein r-Wert-Referenztyp, und die Funktion gibt einen r-Wert-Referenztyp zurück.

Folgende Funktionen vorausgesetzt:

int f();
int& g();
const int& h();
int&& i();

Die folgenden Behauptungen gelten:

static_assert(std::is_same_v<decltype(my_invoke(f)), int>);
static_assert(std::is_same_v<decltype(my_invoke(g)), int&>);
static_assert(std::is_same_v<decltype(my_invoke(h)), const int&>);
static_assert(std::is_same_v<decltype(my_invoke(i)), int&&>);

( Live-Demo , nur Testfall verschieben )

Wenn stattdessen auto&& verwendet wird, wird der Code Probleme haben, zwischen prvalues ​​und xvalues ​​zu unterscheiden.


Vermutlich keine sehr tiefe Antwort, aber es wurde grundsätzlich vorgeschlagen , decltype(auto) für die Ableitung von Rückgabetypen zu verwenden, um Referenzen ableiten zu können, wenn der Rückgabetyp tatsächlich eine Referenz ist (im Gegensatz zu plain auto , die die Referenz niemals ableiten wird, oder auto&& das wird es immer tun).

Die Tatsache, dass es auch für die Variablendeklaration verwendet werden kann, bedeutet nicht unbedingt, dass es bessere Szenarien als andere geben sollte. In der Tat erschwert die Verwendung von decltype(auto) in der Variablendeklaration nur das Lesen des Codes, da für eine Variablendeklaration genau dieselbe Bedeutung vorliegt. Auf der anderen Seite können Sie mit dem auto&& decltype(auto) eine konstante Variable deklarieren, mit decltype(auto) nicht.





decltype-auto