c++classc++17pass-by-referencereference-wrapper

How do I use std::reference_wrapper to store the reference in an std::unordered_map?


I'm trying to learn the use of std::reference_wrapper and std::ref and the use cases of it.

Here is a sample code I wrote. Please help me solve the compilation error. And it would be great if you can explain the issue in depth.

#include <iostream>
#include <limits>
#include <unordered_map>
#include <functional>

class MyClass
{
public:
  struct Data
  {
    int id;

    Data() : id(std::numeric_limits<int>::max()) {}
  };

  std::unordered_map<std::string, Data> keyToData_;

  Data& getOrCreateData(const std::string &key) 
  {
    auto it = keyToData_.find(key);
    if (it == keyToData_.end())
    {
      it = keyToData_.insert({key, Data()}).first;
    }
    return it->second;
  }

  void addBuffer(const std::string &key, std::reference_wrapper<Data> && r)
  {
    buffer_[key] = r;
  }

private:
  std::unordered_map<std::string, std::reference_wrapper<Data>> buffer_;
};

int main(int, char **argv)
{
  MyClass dataManager{};

  auto &r1 = dataManager.getOrCreateData("key1");
  r1.id = 1;

  dataManager.addBuffer("key1", std::ref(r1));
}

I want to store the reference of the data. The data will keep updating in the meantime, it will be like the dirty bit reference of the data. At the end of my program, I would just need to update these values without iterating the whole std::unordered_map. I don't want to create any copy in this process to avoid performance issues, and I'm also avoiding std::shared_ptr as I think using a raw pointer would be just better in this case (I know that the object is not going out of scope).

Is there any better approach?

Here's the error:

.\sample.cpp:30:16:   required from here
C:/msys64/mingw64/include/c++/11.3.0/tuple:1824:9: error: no matching function for call to 'std::reference_wrapper<MyClass::Data>::reference_wrapper()'
 1824 |         second(std::forward<_Args2>(std::get<_Indexes2>(__tuple2))...)
      |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from C:/msys64/mingw64/include/c++/11.3.0/functional:58,
                 from .\sample.cpp:4:
C:/msys64/mingw64/include/c++/11.3.0/bits/refwrap.h:321:9: note: candidate: 'template<class _Up, class, class> std::reference_wrapper<_Tp>::reference_wrapper(_Up&&) [with _Up = _Up; <template-parameter-2-2> = <template-parameter-1-2>; <template-parameter-2-3> = <template-parameter-1-3>; _Tp = MyClass::Data]'
  321 |         reference_wrapper(_Up&& __uref)
      |         ^~~~~~~~~~~~~~~~~
C:/msys64/mingw64/include/c++/11.3.0/bits/refwrap.h:321:9: note:   template argument deduction/substitution failed:
In file included from C:/msys64/mingw64/include/c++/11.3.0/bits/hashtable_policy.h:34,
                 from C:/msys64/mingw64/include/c++/11.3.0/bits/hashtable.h:35,
                 from C:/msys64/mingw64/include/c++/11.3.0/unordered_map:46,
                 from .\sample.cpp:3:
C:/msys64/mingw64/include/c++/11.3.0/tuple:1824:9: note:   candidate expects 1 argument, 0 provided
 1824 |         second(std::forward<_Args2>(std::get<_Indexes2>(__tuple2))...)
      |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from C:/msys64/mingw64/include/c++/11.3.0/functional:58,
                 from .\sample.cpp:4:
C:/msys64/mingw64/include/c++/11.3.0/bits/refwrap.h:326:7: note: candidate: 'constexpr std::reference_wrapper<_Tp>::reference_wrapper(const std::reference_wrapper<_Tp>&) [with _Tp = MyClass::Data]'
  326 |       reference_wrapper(const reference_wrapper&) = default;
      |       ^~~~~~~~~~~~~~~~~
C:/msys64/mingw64/include/c++/11.3.0/bits/refwrap.h:326:7: note:   candidate expects 1 argument, 0 provided

I have tried changing the function declaration like this:

void addBuffer(const std::string &key, Data &r)
{
  buffer_[key] = std::ref(r);
}

Solution

  • std::unordered_map::operator[] requires the mapped_type to be default constructible. Since std::reference_wrapper<Data> (i.e. the mapped_type of std::unordered_map<std::string, std::reference_wrapper<Data>> buffer_;) is not default constructible, you get the compiler error at the following line:

    buffer_[key] = std::ref(r);
    

    The compiler error will go, once you have used, for example, std::unordered_map::emplace to add the key-value to the buffer_, that will insert a new element into the container, constructed in-place using the provided arguments, if no element with the specified key already exists in the container.

    buffer_.emplace(key, std::move(r));
    

    See here: https://gcc.godbolt.org/z/zT1PG3d18


    That being said, I would rather suggest passing the Data& than the r-value to std::reference_wrapper<Data>, because the getOrCreateData() returns the Data&, which is the input to the addBuffer(). No need of an extra set of conversion in between:

    void addBuffer(const std::string& key, Data& r)
    //                                     ^^^^^^^
    {
      buffer_.emplace(key, std::ref(r));
    }
    
    ...
    
    int main()
    {
      // ....
      auto& r1 = dataManager.getOrCreateData("key1");
      // 
      dataManager.addBuffer("key1", r1);
      //                            ^^^
    }