Search code examples
c++dictionaryc++17emplace

Problem understanding the behaviour of std::map try_emplace for a composite key


I have a std::map<CompositeKey, std::string>, where CompositeKey is a class I wrote. This CompositeKey has three int data members, all the constructors, all the copy assignment operators and a friend bool operator<, which compares the sum of the three data members.

I understood how to use emplace and emplace_hint.

For example:

// emplace
std::map<CompositeKey, std::string> my_map;
int first_id = 10, second_id = 100, third_id = 1000;
std::string my_string = "foo";
auto [ insertedIt, success ] = my_map.emplace(std::piecewise_construct,     
                                              std::forward_as_tuple(first_id, second_id, third_id), 
                                              std::forward_as_tuple(my_string));
// emplace_hint
first_id = 5, second_id = 50, third_id = 500;
my_string = "bar";
std::tie(insertedIt, success) = my_map.emplace_hint(insertedIt
                                                    std::piecewise_construct,     
                                                    std::forward_as_tuple(first_id, second_id, third_id), 
                                                    std::forward_as_tuple(my_string));

The only way I was able to use try_emplace was in the version without hint:

first_id = 1, second_id = 10, third_id = 100;
my_string = "foobar";
std::tie(insertedIt, success) = my_map.try_emplace({first_id, second_id, third_id},
                                                   my_string);

My questions are:

  1. Is this the only way to call try_emplace, without hint? If not, how could I call it?
  2. How could I call the try_emplace version with a hint? I made some attempts but always failed.
  3. Is it correct to assume that try_emplace "move" or "copy" the CompositeKey inside the map? I am asking about the behaviour of try_emplace both because I read something similar on another discussion, and because I wrote a verbose copy and move constructors to make a test.

I am sorry, I made my research but do not understand these points from the cppreference documentation


Solution

  • I think you may be misunderstanding the cppreference documentation. In your code, you are always trying to add a key that is not in the map. And you are passing that key as an lvalue reference. The only difference with your two cases is that you are using a hint in the second call. So the two try_emplace versions you are using, according to cppreference, are 1 and 3.

    template< class... Args > pair<iterator, bool> try_emplace( const Key& k, Args&&... args ); (1)   (since C++17)
    
    template< class... Args > iterator try_emplace( const_iterator hint, const Key& k, Args&&... args ); (3)  (since C++17)
    

    Notice:

    • Both versions receive the key as an lvalue reference (Key&).
    • First version returns a pair<iterator, bool, but second version just returns an iterator.

    Now, the text below doesn't tell how you have to build your try_emplace arguments; instead, it says what try_emplace is doing internally.

    1) If a key equivalent to k already exists in the container, does nothing.
       Otherwise, behaves like emplace except that the element is constructed as
    
    value_type(std::piecewise_construct,
               std::forward_as_tuple(k),
               std::forward_as_tuple(std::forward<Args>(args)...))
    

    In short, just call try_emplace passing a CompositeKey object and a string as arguments (and, optionally, an iterator as a hint). The code below calls try_emplace with an lvalue and an rvalue (the second call with a hint), so it would use the versions 1 and 4.

    [Demo]

    std::map<CompositeKey, std::string> my_map;
    
    std::cout << "Case 1:\n";
    auto key1{CompositeKey{10, 100, 1000}};
    auto value1{std::string{"foo"}};
    auto [insertedIt, success] = my_map.try_emplace(key1, value1);
    
    std::cout << "Case 2:\n";
    insertedIt = my_map.try_emplace(insertedIt, {5, 50, 500}, "bar");
    
    // Outputs:
    //
    //   Case 1:
    //       custom ctor
    //       copy ctor
    //   Case 2:
    //       custom ctor
    //       move ctor
    
    1. Is it correct to assume that try_emplace "move" or "copy" the CompositeKey inside the map?

    Whatever you pass is forwarded to try_emplace implementation. In the example above, both CompositeKey arguments are first "custom constructed" ; then, they are passed to try_emplace, where the lvalue is copy constructed while the rvalue is move constructed.