4

I'm trying to hold a polymorphic type as a key in a map.

I came up with the following two structures:

Note that Game is an abstract class and the data structure I use is :

std::unordered_map<gamePtr,int> _allGames;

while gamePtr is a typedef for:

unique_ptr<Game>

template<>
struct std::hash<std::unique_ptr<Game>> {
  size_t operator()(std::unique_ptr<Game> game) const {
    return (std::hash<string>()(std::to_string(game->firstTeamFinalScore()) + game->firstTeam() + game->secondTeam()));
  }

};

struct cmp_games {
  bool operator() (std::unique_ptr<Game> game1, std::unique_ptr<Game> game2) const {  
    return *game1 == *game2;
  }
};

The cmp_games comparator seems to work fine but the std::hash does not because it tries to copy a unique_ptr (Which is ofc impossible) and I've no idea how to get over it. Would love to hear some suggestions (If that is even possible).

EDIT: The comparator also doesn't seem to work properly. how do I make this map work correctly with unique_ptr as a key?

EDIT2:

Came up with:

template<>
struct std::hash<std::unique_ptr<Game>> {
size_t operator()(const std::unique_ptr<Game>& game) const {
     return (std::hash<string>()(std::to_string(game->firstTeamFinalScore()) + game->firstTeam() + game->secondTeam()));
}
};

template<>
struct std::equal_to<std::unique_ptr<Game>> {
bool operator() (const std::unique_ptr<Game>& game1,const std::unique_ptr<Game>& game2) const {

    return *game1 == *game2;
}

};

Should they be enough?

Praetorian
  • 106,671
  • 19
  • 240
  • 328
Rouki
  • 2,239
  • 1
  • 24
  • 41
  • Pass the `unique_ptr` by reference to `operator()`? –  Jan 07 '14 at 06:27
  • Isn't `std::hash` defined for `std::unique_ptr` by the Standard anyway? It's on cppreference: http://en.cppreference.com/w/cpp/memory/unique_ptr/hash – jogojapan Jan 07 '14 at 06:28
  • @jogojapan It seems to me that the standard hash hashes by the pointer address while the questioner wants to hash by the object's value the pointer points to, see their `cmp_games` implementation. –  Jan 07 '14 at 06:36
  • @Nabla Oh sure, what I mean is just that the implementation in the Standard library may provide insight into how to pass the `unique_ptr` argument. It must definitely be possible (and yes, using references is a good candidate). – jogojapan Jan 07 '14 at 06:48
  • What's the problem with the comperator? Please explain in what respect the *comparator also doesn't seem to work properly*? – Oswald Jan 07 '14 at 06:54
  • It seems that the 3rd parameter for unordered map is a hash while the 4th is a comparator. Question is how do I pass the comparator as a 4th argument without passing the 3rd? maybe a default value for the hash? – Rouki Jan 07 '14 at 06:55
  • @Rouki You don't need to provide either the 3rd or the 4th template arguments explicitly if you follow the advice in either answer below – Praetorian Jan 07 '14 at 06:57
  • I agree about the hash one. but what about the compare? Can I also define a specialization for the comparator? and if yes, how? – Rouki Jan 07 '14 at 06:58
  • Yes, the default value for the hash. And that's`std::hash>`. – Oswald Jan 07 '14 at 06:59
  • Edited the question with the things Ive done – Rouki Jan 07 '14 at 07:02
  • @Rouki I've updated my answer with the required comparison operators. – Praetorian Jan 07 '14 at 07:27

2 Answers2

4

The standard provides a specilization so that std::hash<unique_ptr<T>> is the same as std::hash<T*>. So provide a specialization for std::hash<Game *>. For example:

#include <iostream>
#include <memory>
#include <unordered_map>
#include <cstdlib>

struct foo 
{
    foo(unsigned i) : i(i) {}
    unsigned i;
};

namespace std {

template<>
struct hash<foo *>
{
    size_t operator()(foo const *f) const
    {
        std::cout << "Hashing foo: " << f->i << '\n';
        return f->i;;
    }
};

}

int main()
{
    std::unordered_map<std::unique_ptr<foo>, int> m;
    m.insert(std::make_pair(std::unique_ptr<foo>(new foo(10)), 100));
    m.insert(std::make_pair(std::unique_ptr<foo>(new foo(20)), 200));
}

Live demo


Another option is to change your existing std::hash specialization so that it takes the unique_ptr by reference.

size_t operator()(std::unique_ptr<Game> const& game) const
//                                      ^^^^^^ no more copying

EDIT: std::unique_ptr provides comparison operators that compare the managed pointers. If you want the unordered_map to test the Game objects themselves for equality, provide an operator== overload instead of specializing std::equal_to

inline bool operator==(const std::unique_ptr<Game>& game1, 
                       const std::unique_ptr<Game>& game2) 
{
    return *game1 == *game2;
}

This, in turn, requires that you've provided an equality operator for Game (or you could just add the logic to the function above).

inline bool operator==(Game const& game1, Game const& game2)
{
    return // however you want to compare these
}
Praetorian
  • 106,671
  • 19
  • 240
  • 328
0

Pass the game by const reference into std::hash::operator():

template<>
struct std::hash<std::unique_ptr<Game>> {
    size_t operator()(const std::unique_ptr<Game>& game) const;
}

The same applies to cmp_games::operator().

Oswald
  • 31,254
  • 3
  • 43
  • 68