Was tut "-> decltype()" hinter einer Funktion?



  • Hallo,

    hier ein kurzer Ausschnitt aus STL's vortrag "Don't help the Compiler":

    template <> struct plus<void> {
      template <class T, class U>
        auto operator()(T&& t, U&& u) const
        -> decltype(forward<T>(t) + forward<U>(u)) {
        return forward<T>(t) + forward<U>(u);
      }
    }
    

    Ich verstehe (halbwegs) was auto und decltype machen, auch deren unterschied, allerdings verstehe ich das obere Beispiel nicht, wo ist da der unterschied zu

    decltype(forward<T>(t) + forward<U>(u)) operator()(T&& t, U&& u) const;
    

    ? Was genau tut das "-> decltype()" dahinter, ich nehme an, es hat was mit dem returntype zutun?



  • Huch, sowas haben sie uns in unserem C-Kurs in den 80ern nicht beigebracht.
    Wäre cool wenn das jemand erklären könnte Also komplett und so.


  • Mod

    tkausl schrieb:

    Ich verstehe (halbwegs) was auto und decltype machen, auch deren unterschied, allerdings verstehe ich das obere Beispiel nicht, wo ist da der unterschied zu

    decltype(forward<T>(t) + forward<U>(u)) operator()(T&& t, U&& u) const;
    

    Der Unterschied ist, dass dein Vorschlag nicht funktioniert, da u und t an der Stelle noch nicht deklariert wurden.

    ? Was genau tut das "-> decltype()" dahinter, ich nehme an, es hat was mit dem returntype zutun?

    Korrekt. Dieses Zusammenspiel von auto und "->" ist eine Möglichkeit, den Rückgabetyp erst hinter der Deklaration der Funktionsparameter anzugeben. Eben aus genau dem hier beschriebenen Grund, weil man sonst keine Rückgabewerte angeben könnte, die von den Parametern abhängen.



  • Ah, das hab ich mir fast gedacht, aber sollte ein decltype(auto) als Rückgabewert dann nicht das gleiche Ergebnis bringen? Ich meine, Type Deduction ist ja nicht blöd, der Compiler sieht ja die Expression die zurückgegeben wird.


  • Mod

    tkausl schrieb:

    Ich meine, Type Deduction ist ja nicht blöd, der Compiler sieht ja die Expression die zurückgegeben wird.

    Das wurde aus Zeitgründen gestrichen 🙂 . Kein Scherz. Aber es ist in C++14 gekommen. Wenn dein Compiler aktuell genug ist, kannst du das also tatsächlich machen. Die Syntax ist das, was man intuitiv erwarten würde:

    auto foo(int i) { return i + 1; }
    

    decltype(auto) funktioniert aber auch. Wobei, wenn ich mich recht entsinne, die genauen Regeln jeweils leicht unterschiedlich sind, wie die Type-Deduction genau durchgeführt wird.

    PS: Hat irgendwer sonst noch das Gefühl, dass dieser Thread nicht so ganz ernst gemeint ist? Ich fühle mich ein bisschen so, als wolle jemand meine C++14-Kompetenz testen.



  • SeppJ schrieb:

    Wobei, wenn ich mich recht entsinne, die genauen Regeln jeweils leicht unterschiedlich sind, wie die Type-Deduction genau durchgeführt wird.

    auto wirft constness und referenceness weg, decltype tut das nicht, daran kann ich mich noch erinnern 🙂

    SeppJ schrieb:

    PS: Hat irgendwer sonst noch das Gefühl, dass dieser Thread nicht so ganz ernst gemeint ist? Ich fühle mich ein bisschen so, als wolle jemand meine C++14-Kompetenz testen.

    Die Frage war ernst gemeint 😕


  • Mod

    auto wirft constness und referenceness weg, decltype tut das nicht, daran kann ich mich noch erinnern

    Jupp. Irgendwas in dieser Richtung. Ich bin aber zu faul, die genauen Regeln nach zu schauen, traue mich aber gleichzeitig nicht, diese ohne Nachgucken anzugeben.

    tkausl schrieb:

    Die Frage war ernst gemeint 😕

    Es fühlt sich halt irgendwie so an, als würdest du die Antworten bereits kennen, weil deine Fragen derart perfekt zu gewissen Antworten überleiten. Kann natürlich auch bloß Zufall (oder exzellentes Sprachgefühl 👍 ) sein und ich bin zu paranoid 🙂 .



  • SeppJ schrieb:

    Es fühlt sich halt irgendwie so an, als würdest du die Antworten bereits kennen, weil deine Fragen derart perfekt zu gewissen Antworten überleiten. Kann natürlich auch bloß Zufall (oder exzellentes Sprachgefühl 👍 ) sein und ich bin zu paranoid 🙂 .

    Liegt vermutlich daran, dass ich sehr gerne Vorträge von Scott Meyers, Stephan T. Lavavej und anderen (von der CppCon und auch andere), usw. anschaue, da ich diese sehr Spannend finde, dadurch gewisse "kniffe" aus dem Sprachkern lerne, gleichzeitig aber noch nichtmal die Grundlagen "richtig" gelernt habe, dadurch tuen sich teilweise Verständnisfragen auf, wo ich mir manchmal bereits denken kann, was etwas bedeutet, aber es eben gerne genau wissen möchte.



  • SeppJ schrieb:

    funktioniert aber auch. Wobei, wenn ich mich recht entsinne, die genauen Regeln jeweils leicht unterschiedlich sind, wie die Type-Deduction genau durchgeführt wird.

    😞 *weinender otze*



  • Grade in einem anderen Vortrag von declval gehört, welches seit C++11 existiert und wenn ich so drüber nachdenke, sollte

    decltype(forward<T>(declval<T>()) + forward<U>(declval<U>())) operator()(T&& t, U&& u) const;
    

    funktionieren, ohne die Funktionsargumente zu benötigen.



  • Ja, Meyers erwähnt in seinen Vorträgen gerne dass es - IIRC - mindestens 3 verschiedene Type-Deduction Regeln bei C++ gibt.
    Ich glaube es war bei Template-Parametern, auto und decltype .

    Oder gibt's noch mehr? k.A., auf jeden Fall Chaos.

    SeppJ schrieb:

    auto foo(int i) { return i + 1; }
    

    Ist das eigentlich das selbe wie

    auto foo(int i) -> auto { return i + 1; }
    

    ?


  • Mod

    decltype(auto) hat einen wichtigen Unterschied zu auto selbst: Es berücksichtigt die Wertkategorie des Rückgabetyps.

    [dcl.spec.auto]/7 schrieb:

    If the placeholder is the decltype(auto) type-specifier, the declared type of the variable or return type of the function shall be the placeholder alone. The type deduced for the variable or return type is determined as described in 7.1.6.2, as though the initializer had been the operand of the decltype .

    Die Regeln für decltype sagen jedoch aus, dass für Ausdrücke die keine Identifier sind, ggf. Referenzen deduziert werden - abhängig von Wertkategorie des Ausdrucks. I.e.

    - Für xvalues werden Rvalue-Referenzen deduziert
    - ... und für lvalues Lvalue-Referenzen.
    - Bei prvalues ist es einfach der Typ.

    Das ist nützlich wenn wir einen proxycall durchführen:

    decltype(auto) f(auto&&... args) {
        return myFunctionObject(std::forward<decltype(args)>(args)...);
    }
    

    Gibt myFunctionObject nun bspw. einen std::string& zurück, so gibt f auch einen std::string& zurück. Hätten wir hingegen auto benutzt, so gäbe f std::string zurück, es sei denn wir hätten trailing-return-types benutzt - wofür wir aber zu Faul sind.

    PS:
    Der trailing-return-type in

    auto foo(int i) -> auto { return i + 1; }
    

    ist zwar gültig aber völlig überflüssig. Also ja: Ist dasselbe wie ohne.

    Was genau tut das "-> decltype()" dahinter, ich nehme an, es hat was mit dem returntype zutun?

    Es ist der return type, aber wie bereits angedeutet verwendet man darin Dinge wie Klassenmember oder Funktionsparameter. Der Standard selbst bringt hier ein Beispiel:

    [dcl.fct] schrieb:

    [ Note: Typedefs and trailing-return-types are sometimes
    convenient when the return type of a function is complex. For example, the function fpif above could have been declared

    typedef int IFUNC(int);
    IFUNC* fpif(int);
    

    or

    auto fpif(int)->int(*)(int);
    

    A trailing-return-type is most useful for a type that would be more complicated to specify before the declarator-id:

    template <class T, class U> auto add(T t, U u) -> decltype(t + u);
    

    rather than

    template <class T, class U> decltype((*(T*)0) + (*(U*)0)) add(T t, U u);
    

    — end note ]


Anmelden zum Antworten