4

I can't figure out how to get a std::string reference into an std::unordered_map using a std::reference_wrapper. Per the following link I understand I need to overload operator==.

Why can template instances not be deduced in `std::reference_wrapper`s?

However, I can't figure out how to write operator== such that it will take a const std::reference_wrapper. If the wrapper were not const it would not be a problem.

Using a char instead of std::string works fine (doesn't require overloading operator==).

Code:

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

bool operator==(const std::reference_wrapper<std::string> lhs,
                const std::reference_wrapper<std::string> rhs)
{
    return std::equal_to<std::string>()(lhs.get(), rhs.get());
}

int main(){
    char        chr('a');
    std::string str("b");
    int         num(1);

    // this works (char)
    std::unordered_map<std::reference_wrapper<char>, int, std::hash<char>> charMap;
    std::pair<std::reference_wrapper<char>, int> charPair(chr , num);
    charMap.insert(charPair);
    std::cout << "charMap works.  Output: " << charMap[chr] << std::endl;

    // does not work (std::string)
    std::unordered_map<std::reference_wrapper<std::string>, int, std::hash<std::string>> stringMap;
    std::pair<std::reference_wrapper<std::string>, int> stringPair(str , num);
    stringMap.insert(stringPair);  // compile error
}

Compile error:

error: no match for ‘operator==’ (operand types are ‘const std::reference_wrapper<std::__cxx11::basic_string<char> >’ and ‘const std::reference_wrapper<std::__cxx11::basic_string<char> >’)
       { return __x == __y; }
Community
  • 1
  • 1
dontarius
  • 99
  • 1
  • 8

1 Answers1

11

You cannot provide your own overload for operator== for non-user-defined types. That is, at best, undefined behavior. However, you don't need to do that here. std::unordered_map has five template parameters:

template<
    class Key,
    class T,
    class Hash = std::hash<Key>,
    class KeyEqual = std::equal_to<Key>,
    class Allocator = std::allocator< std::pair<const Key, T> >
> class unordered_map;

See the 4th one? That's what you want. You need to provide a function to compare. Luckily, you can use std::hash and std::equal_to like this:

std::unordered_map<
    std::reference_wrapper<std::string>,
    int,
    std::hash<std::string>,
    std::equal_to<std::string>
> stringMap;
Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Yes, this is the right solution. Unfortunately, `std::reference_wrapper` doesn't specify `operator==` (it would be easy to just forward it to `operator==` of the underlying type) and doesn't provide a hashing method, either. So one has to be always explicit, when creating an `unordered_map` or `unordered_set` with these. – Kai Petzke Sep 04 '20 at 18:06