Search code examples
c++stlstdmapc++23

How do I initialize an inplace key value pair in std::map


I want to insert key value pairs into a std::map with uint32_t as the key type and a custom type for values. Based on the documentation of std::map, I wrote the following code:

#include <cstdint>
#include <iostream>
#include <map>

struct Values
{
        uint32_t a = 0;
        uint32_t b = 0;

        Values(uint32_t _x, uint32_t _y): a(_x), b(_y)
        {
            std::cout << __func__ << "(" << _x << ", " << _y ")" << std::endl;
        }
};

int main()
{
        std::map<uint32_t, Values> data;
        for(uint32_t i = 0; i < 100; i++)
                data.emplace(i, std::forward_as_tuple(i, i));

        return 0;
}

I'm compiling the code with gcc by running

gcc -O3 -std=c++23 -Wconversion -Wall -Wextra -Wpedantic -fanalyzer 

However, this code doesn't compile.

Here's the link to compiler explorer where you can see the full output of gcc.

gcc is complaining about:

error: no matching function for call to 'construct_at(std::pair<const unsigned int, Values>*&, unsigned int&, std::tuple<unsigned int&, unsigned int&>)'

Why am I getting this error and how can I fix this?


Solution

  • std::map<uint32_t, Values> data;
    data.emplace(i, std::forward_as_tuple(i, i));
    

    emplace() tries to construct the map's value_type (i.e. pair<uint32_t, const Values>) using the bag of arguments you passed to emplace(). So, it's going to be trying to do this:

    auto kv = pair<uint32_t, const Values>(i, std::forward_as_tuple(i, i));
    

    That's not right. If you're trying to use pair's perfect-forwarding constructor, you need to say instead:

    auto kv = pair<uint32_t, const Values>(
      std::piecewise_construct,
      std::forward_as_tuple(i), // args for the first element
      std::forward_as_tuple(i, i) // args for the second element
    );
    

    which means your emplace() call needs to look like:

    data.emplace(
      std::piecewise_construct,
      std::forward_as_tuple(i), // args for the first element
      std::forward_as_tuple(i, i) // args for the second element
    );
    

    Alternatively, if you don't mind constructing the Values a little earlier, you can just write:

    data.emplace(i, Values(i, i));
    

    which corresponds to:

    auto kv = pair<uint32_t, const Values>(
      i, // first element
      Values(i, i) // second element
    );
    

    Or even:

    data.insert(std::make_pair(i, Values(i, i)));