NRVO or not?



  • @sewing sagte in NRVO or not?:

    Klar, wenn ich Referenzen auf temporaries nehme, ist das schlecht, aber auf der Call Side wird ja mit nem structured binding by value gearbeitet.
    Daher müsste das ja funktionieren oder?

    Sobald du die Funktion verlässt, sind die Objekte kaputt. Da hilft auch keine Value Zuweisung auf Caller Seite. Im Gegenteil, genau das ist dann UB (zumindest im Pointer-Fall; dangling Referenzen sind ja per se UB).
    Zumal diese Value Zuweisung auch nur für dein std::ref Object gilt. Wie willst du denn direkt an das darunter liegende Objekt ran kommen?



  • Danke vielmals. Ich finde mit copy elision ist es manchmal nicht so einfach zu erkennen, wann was gilt.

    RVO funktioniert ja auch bei Verzweigungen innerhalb einer Funktion mit mehreren returns. Allerdings muss in jedem Zweig die gleiche Instanz zurückgegeben werden, also

    auto foo() {
      Widget w;
      if (false)
        //do sth. with w
        return w;
      else
        // do sth. else with w
        return w;
    }
    

    Wie verhält es sich aber bei einem optional als rückgabewert?

    
    std::optional<Widget> foo() {
      Widget w;
      if (false)
        return std::nullopt;
      else
        return std::make_optional(w);
    }
    

    Offensichtlich werden hier ja zwei unterschiedliche Objekte zurückgegeben, je nach Bedingung


  • Mod

    Was möchtest du wissen? Du weißt doch schon, dass stets die gleiche Instanz zurück gegeben werden muss.



  • das bedeutet, dass ich bei der realistischen Anwendung von optional für die maybe Rückgabe eines Objekts durch eine Funktion immer mindestens einen move habe - trotz RVO



  • make_optional forwarded an den Widget-Constructor. Wenn du nicht w, sondern die Widget-Konstruktor-Argumente übergibst, wird inplace konstruiert. Geht natürlich nur sinnvoll, wenn du w nicht vorher erzeugt hast.

    Und in deinem Code kannst du w moven statt zu kopieren.



  • Interessant finde ich dahingehend auch, dass

    #include <iostream>
    #include <optional>
    
    struct Widget {
      Widget() { std::cerr << "Default ctor" << std::endl; }
      Widget(int i) { std::cerr << "custom ctor" << std::endl; }
      Widget(Widget const &) { std::cerr << "copy ctor" << std::endl; }
      Widget(Widget &&) { std::cerr << "move ctor" << std::endl; }
      Widget &operator=(Widget const &) { std::cerr << "copy assign" << std::endl; }
      Widget &operator=(Widget &&) { std::cerr << "move assign" << std::endl; }
      int i_;
    };
    
    std::optional<Widget> foo() {
      // auto o = std::make_optional<Widget>(1); // custom ctor und move ctor
      auto o = std::make_optional(1); // custom ctor
      if (false)
        return std::nullopt;
      else 
        return o;
    }
    
    int main(){
      auto f = foo();
    }
    

    nicht moved, aber die auskommentierte Variante mit dem expliziten template Argument noch einen move ausführt.

    es kommt doch der overload

    template< class T, class... Args > 
    constexpr std::optional<T> make_optional( Args&&... args );
    

    zum tragen hier, wobei der erste template Parameter explizit angegeben wird. Wieso dann der Unterschied?

    Klar findet hier eine implizite Konvertierung statt von einem std::optional<int> zu einem optional<Widget> beim return, aber dennoch kein move, was mich wundert



  • Ich würde mal darauf tippen, dass der compiler hier nicht optimieren darf, weil der Move CTor Sideeffects hat.


Anmelden zum Antworten