std::map::try_emplace



  • Die Funktion istja neu mit C++17 dazu gekommen und hat den Vorteil, dass im falle, dass der Key schon in der Map existiert, der mapped Type gar nicht erst konstruiert wird (im gegenteil zu std:🗺:emplace.

    Nun habe ich folgende Klasse:

    struct Foo{
       Foo(){ std::cout << "Default ctor\n";}
       ~Foo(){ std::cout << "Dtor \n";}
    };
    

    und möchte jetzt über die Map laufen und nur wenn der Key noch nicht vorhanden ist, eine Instanz von std::vector<Foo> einfügen.

    std::map<size_t,std::vector<Foo>> map_;
    
    for (size_t i = 0; i < 5, ++i) 
      map_.try_emplace(i, Foo(());
    
    for (size_t i = 0; i < 5, ++i) 
      map_.try_emplace(i, Foo(());
    

    jedoch ruft er auch beim zweiten Schleifendurchlauf den Ctor von Foo auf nur um danach unmittelbar den Destructor aufzurufen.

    Was mache ich hier falsch?

    danke für eure Hilfe



  • Sewing schrieb:

    und möchte jetzt über die Map laufen und nur wenn der Key noch nicht vorhanden ist, eine Instanz von std::vector<Foo> einfügen.

    Und warum fügst du dann eine Instanz von Foo ein?



  • Ich füge implizit einen ein-elementigen vector ein, die Signatur ist

    pair<iterator, bool> try_emplace(key_type&& k, Args&&... args);
    

    hier das gleiche

    map_.try_emplace(i, std::vector<Foo>{ {} });
    


  • Die Frage war nicht "wie geht das" sonder "warum"?

    map_.try_emplace(i, {} };
    

    ?



  • Nein, ich hab mich wohl etwas unklar ausgedrückt ; )

    Wenn der key schon enthalten ist, wird ja kein Vector konstruiert, wohl aber ein default-initialized Foo Object nur um es dann direkt wieder zu verwerfen.

    Und ich wüsste gerne, wie ich das so hinbekomme, dass nicht einmal Foo angelegt wird, im Falle eines bereits vorhandenen Keys



  • Da die Parameter immer zuerst ausgewertet werden, bevor ein Funktionsaufruf stattfindet, mußt du explizit dann selber die Abfrage durchführen:

    if (map_.count(i) == 0) // oder !count(i)
      map_.emplace(i, Foo());
    

    Als Abhilfe könntest du selber eine Funktion mit einem Funktionsparameter erzeugen und diese mit einem Lambda-Ausdruck aufrufen:

    map_try_emplace(map, [] { return Foo(); });
    


  • Wäre nicht die einfachere Lösung, einfach {} zu try_emplacen und den Rückgabewert von try_emplace zu nutzen?

    auto [it, inserted] = map_.try_emplace(i, {});
    if (inserted) {
      it->reserve(...);
      it->emplace_back(...);
    }
    

    oder so ähnlich?

    Ich denke, man sollte den doppelten map-Lookup aus der 1. Alternative von The69 vermeiden.



  • aber eure Vorschläge gehen ja vollkommen vorbei an dem, wofür try_emplace gedacht ist

    habe in einem Buch folgendes gefunden

    http://666kb.com/i/dqug8l5meu77z7za5.jpg



  • ich glaube das problem ist, dass ich versuche den vector mit rvalues zu initialisieren. Das Problem lässt sich wohl lösen indem man den vector in eine struct wrapped und die struct wird ja dann nur constructed, wenn der Key nicht schon vorhanden ist



  • Ich glaube, wir reden irgendwie aneinander vorbei. Erzeugt wird doch immer etwas, weil du ein Argument erzeugst.

    try_emplace heißt doch einfach nur, wenn ein Key existiert, bleibt der Container unverändert. Das Argument spielt dann dabei gar keine Rolle (für den Container).

    PS: Welches Buch ist das?



  • Eben nicht!

    der besondere Reiz von try_emplace liegt in der Tatsache begründet, dass

    "will not construct the object associated with the key, if the key already exists..."



  • Sewing schrieb:

    Eben nicht!

    der besondere Reiz von try_emplace liegt in der Tatsache begründet, dass

    "will not construct the object associated with the key, if the key already exists..."

    Richtig, das bezieht sich aber nicht auf den Funktionsaufruf, sondern auf das was intern in der Funktion try_emplace passiert.

    Wie willst du bei einem Funktionsaufruf verhindern, dass ein Arguement erzeugt wird, wenn du eins übergeben willst und die Funktion eines erwartet: fkt( Foo() )?

    Ich sehe gerade, dass Th69 genau das auch schon geschrieben hat 🤡



  • Doppelt hält besser - vllt. versteht Sewing es ja jetzt!?



  • habs jetzt hoffe ich verstanden:

    std::map<int,std::string> m_ {1, "myString"};
    
    m_.try_emplace(1, "Test");
    

    erschafft zwar nen rvalue "Test" aber nie ein std::string mitels entsprechendem ctor aufruf. In meinem anfänglichen Beispiel würde also nie ein std::vector<Foo> erschaffen, wohl aber ein temporary Foo.

    Hintergrund ist, dass mein vector so eine Art std::anys enhält, die ich dann jeweils mit verschiedenne Objekten iniitalisieren will. Diese Objekte werden dann aber eben jedes Mal schon erzeugt, nur der std::vector eben nicht

    Habe mir jetzt so beholfen

    struct Collection{
       Collection() : anys_{{Foo{}, Foo{}}}
    
    std::vector<Any> anys_;
    };
    
    std::map<int,Collection> m_;
    
    m_.try_emplace(1, {});
    

    auf diese Weise müsste es funktionieren oder?



  • Nein, das geht so nicht.

    Mit deinem Code:

    m_.try_emplace(1, Collection{});
    

    Noch BEVOR try_emplace aufgerufen wird, muss wird ja die beiden Argumente, also 1 und auch dein Collection-Objekt erzeugt (das dann ja im Constructor 2x Foo erzeugt).

    Da try_emplace die Argumente ja an den Constructor weiterleitet, solltest du hier einfach KEIN 2 Argument angeben, da dein Constructor ja auch kein Argument hat.

    Schau dir das mal an:

    #include <vector>
    #include <map>
    #include <iostream>
    
    struct Foo {
       Foo() { std::cout << "Foo create\n"; }
    };
    
    struct Collection{
       Collection() : anys_{{Foo{}, Foo{}}} {}
       std::vector<Foo> anys_;
    };
    
    int main() {
       std::map<int,Collection> m;
       std::cout << "Erfolgreiches emplace\n";
       m.try_emplace(1);
       std::cout << "Emplace, wenn schon existiert\n";
       m.try_emplace(1);
    }
    


  • also grundsätzlich ist bei meinem vorgehen da composition in einer struct die richtige wahl oder?

    hatte meinen code oben schon editiert zu

    m_.try_emplace(1, {});
    

    leeres Klammerpaar, das müsste ja auch funktionieren nehnme ich an

    und wenn ich als argument nen string übergeben möchte

    struct Collection{
       Collection(std::string& s) : anys_{{Foo{s}, Foo{s}}}
    
    std::vector<Any> anys_;
    };
    
    std::map<int,Collection> m_;
    
    m_.try_emplace(1, "MyString");
    

    sollte ebenfalls funktionieren oder? das erzeugt ja nur nen temporary string, aber das Collection Objekt wird mithilfe dieses strings nur erzeugt, wenn der key nicht schon vorhanden ist

    danke für eure Hilfe ; )