Lambda: pass-by-value



  • Hallo!

    was ich mich oft frage, ist, wann es sinnvoll ist, Parameter von Lambdas als Wert oder Referenz zu übergeben. Beispiel: Verwendung von nem Lambda-Prädikat in std::find_if . Angenommen, ich möchte damit etwas in einem Container, welcher Structs mit ca. 5 bis 10 ints beinhaltet, suchen:

    struct A
    {
        int a, b, c, d, e, f, g;
    }
    
    // ...
    
    vector<A> vec;
    
    // ...
    
    auto it = find_if(vec.begin(), vec.end(), [](A  element) { return element.c == 42; }); // 1
    auto it = find_if(vec.begin(), vec.end(), [](A& element) { return element.c == 42; }); // 2
    

    Kann man sich auch für große A darauf verlassen, dass der Compiler bei Version 1 die Kopie wegoptimiert, oder sollte man doch lieber Version 2 benutzen?



  • Ich würd ne const-Referenz dranschreiben, dann hast du's quasi amtlich. Der Einheitlichkeit halber würde ich dir übrigens auch begin(vec), end(vec) als Schreibweise für den Bereich antragen, also

    auto it = find_if(begin(vec), end(vec), [](A const &element) { return element.c == 42; });
    

  • Mod

    Bloops schrieb:

    Hallo!

    was ich mich oft frage, ist, wann es sinnvoll ist, Parameter von Lambdas als Wert oder Referenz zu übergeben. Beispiel: Verwendung von nem Lambda-Prädikat in std::find_if . Angenommen, ich möchte damit etwas in einem Container, welcher Structs mit ca. 5 bis 10 ints beinhaltet, suchen:

    struct A
    {
        int a, b, c, d, e, f, g;
    }
    
    // ...
    
    vector<A> vec;
    
    // ...
    
    auto it = find_if(vec.begin(), vec.end(), [](A  element) { return element.c == 42; }); // 1
    auto it = find_if(vec.begin(), vec.end(), [](A& element) { return element.c == 42; }); // 2
    

    Kann man sich auch für große A darauf verlassen, dass der Compiler bei Version 1 die Kopie wegoptimiert, oder sollte man doch lieber Version 2 benutzen?

    Bei solchen einfachen Prädikaten dürfte eine (const-)Referenz-Übergabe im allgemeinen die richtige Wahl sein (im Falle von primitiven Datentypen dürfte es egal sein). Da das übergebene Argument (nahezu) stets ein lvalue ist (=derefenzierter Iterator), kommt es nicht zu einer unnötigen zusätzlichen Indirektion.
    Die Eliminierung der Kopie im ersten Fall hingegen basiert auf unveränderten beobachtbaren Verhalten (as-if), setzt also entsprechende Einstellungen voraus, und ist ohne Inlining kaum durchführbar.



  • Vielen Dank für die Antworten!

    camper schrieb:

    Da das übergebene Argument (nahezu) stets ein lvalue ist (=derefenzierter Iterator), kommt es nicht zu einer unnötigen zusätzlichen Indirektion.

    Klingt plausibel, daran hab ich gar nicht gedacht.

    camper schrieb:

    Die Eliminierung der Kopie im ersten Fall hingegen basiert auf unveränderten beobachtbaren Verhalten (as-if), setzt also entsprechende Einstellungen voraus, und ist ohne Inlining kaum durchführbar.

    Hmmm... Sind Lambdas nicht automatisch geinlined? Falls nein, wie stellt man das an?


  • Mod

    Bloops schrieb:

    Hmmm... Sind Lambdas nicht automatisch geinlined? Falls nein, wie stellt man das an?

    Das wird durch den Standard nicht festgelegt, beobachtbar ist das sowieso nicht.
    Der generierte operator() des closure-Typs ist eine inline Funktion, das ist aber nur eine Aussage in Hinblick auf die ODR.


Anmelden zum Antworten