Search code examples
c++referenceimplicit-conversionreference-wrapper

Incorrect understanding of implicit conversion from reference to std::reference_wrapper or compiler bug?


I'm confused about some code involving references and std::reference_wrapper and I'm not clear at all whether this is my fault since I'm misunderstanding how reference wrapper works or if I hit a compiler error.

I have a simple map, pairing a reference to an object from a complex class to a value:

std::unordered_map<Object const &obj, int32_t value> myMap;

(for simplicity, I explicitly left out the hash and equal functors required by the map to compile)

Since I can't directly use the reference in the map, I use a reference wrapper to it:

std::unordered_map<std::reference_wrapper<const Object> obj, int32_t value> myMap;

Now, my map is populated in a function such as:

void myFunction(Object const &obj, int32_t value)
{
    ...
    myMap.emplace(std::make_pair(obj, value));
}

but that code, although compiles, doesn't work. However, this one works as expected:

void myFunction(Object const &obj, int32_t value)
{
    ...
    myMap.emplace(std::pair<std::reference_wrapper<const Object>, int32_t>(obj, value));
}

(notice in this second version I'm still not explicitly building a reference wrapper to obj)

So, my doubt is:

Am I misunderstanding something about the usage of reference wrapper? There's no implicit conversion from reference to reference_wrapper? If not, why does the code compile in the first place?

Or is this a known issue/flaw in std::make_pair not being capable to correctly deduct the types passed to it?


Solution

  • std::make_pair never(1) deduces a reference type as one of the types in the pair, it always produces value types. So the call to emplace receives a temporary std::pair<Object, int32_t> as argument. The reference wrapper is then initialised from this temporary, meaning that it will bind to the first member of this temporary pair.

    In the second case, the temporary is of type std::pair<std::reference_wrapper<const Object>, int32_t> (since you've explicitly requested that), which means that there's a reference wrapper in the temporary pair, bound directly to obj. The reference wrapper inside the map is then copied from this temporary reference wrapper and thus refers directly to obj.

    (1) "Never" is not true here. There's one exception: when one of the arguments to std::make_pair is of type std::reference_wrapper<T>, the type in the pair will be decuced to T&. Which actually provides the correct solution for you: wrap obj in std::cref inside the make_pair call:

    myMap.emplace(std::make_pair(std::cref(obj), value));
    

    This will then cause the temporary pair to contain a reference, and the reference_wrapper inside the map will bind to its referred object (obj), which is precisely what you want.