std::hash specialisation for my own class and use it inside the class

Viewed 483

I define a class Foo, and would like to have a public member function that takes std::unordered_set<Foo> as parameters type.

To be able to use std::unordered_set<Foo>, I have to specialize std::hash<Foo> in namespace std.

That's ok if I do not try to use std::unordered_set<Foo> as paramter type in Foo member functions.

However, once I want to use std::unordered_set<Foo> as parameter type in Foo member functions, I Have a problem to define the specialization std::hash<Foo>. If I do it after the Foo declaration, there is an error on Foo declaration because std::hash<Foo> is not defined. It a move std::hash<Foo> definition before, it does not work either because now Foo is unknown. Forward declaration of Foodoes not work in such situation.

Any ideas how to resolve this ?

Here is an example of such a class

class Foo
{
public:
  std::unordered_set<Foo>::iterator findClosest(std::unordered_set<Foo> const &others)
  {
    return std::end(others);
  }

  size_t hashValue() const {
    return std::hash<int>()(m_Member);
  }

private:
  int m_Member;
};

namespace std
{
  template <>
  struct hash<Foo>
  {
    size_t operator()(Foo const & bar) const
    {
      return bar.hashValue();
    }
  };
}

Using answers below, here is the code I finally use (I need to place some MY_EXPORT_MACRO because there is dlls):

In file Foo.h

class Foo;

namespace std
{
  template <>
  struct MY_EXPORT_MACRO hash<Foo>
  {
    size_t operator()(Foo const &bar) const;
  };
}

class MY_EXPORT_MACRO Foo
{
public:
  Foo const *findClosest(std::unordered_set<Foo> const &others);

  size_t hashValue() const
  {
    return std::hash<int>()(m_Member);
  }

  bool operator==(const platypus::Segment2D &other) const
  {
    return m_Member == other.m_Member;
  }

private:
  int m_Member;
};

In file Foo.cpp

size_t std::hash<Foo>::operator()(Foo const &bar) const
{
  return bar.hashValue();
}

Foo const *Foo::findClosest(std::unordered_set<Foo> const &others)
{
  Foo const *closest = nullptr;
  std::unordered_set<Foo>::const_iterator closestIt =
    std::min_element(std::begin(others), std::end(others), [this](Foo const &lhs, Foo const &rhs) {
      return std::abs(this->m_Member - lhs.m_Member) < std::abs(this->m_Member - rhs.m_Member);
    });

  if (closestIt != std::end(others))
  {
    closest = &(*closestIt);
  }

  return closest;
}
4 Answers

The real problem in your example is that you want to use std::unordered_set<Foo>::iterator as return type. This requires that unordered_set<Foo> is fully instantiated, which requires Foo (and std::hash<Foo>) to be a complete class. But Foo is only a complete class at the end of its definition. Note that this problem does not exist with the by-reference function parameter, where the compiler does not have to fully instantiate the referenced class.

As originally suggested by @MrTux, you can fix everything else with forward declarations:

class Foo;

template<>
struct std::hash<Foo>;

If you return a Foo* instead, everything works:

Demo

Whether that works for your design is another question.


I should note that you can have std::hash<Foo> be fully defined before Foo's definition:

class Foo;

namespace std
{
  template <>
  struct hash<Foo>
  {
    size_t operator()(Foo const & bar) const;
  };
}

class Foo { /* ... */ };

size_t std::hash<Foo>::operator()(Foo const & bar) const
{
    return bar.hashValue();
}

That would allow you to return e.g. std::unordered_set<Foo> from methods in Foo, but it still does not fix the core issue of Foo being incomplete (and thus std::unordered_set<Foo>::iterator being unavailable) during its own definition.

I see that you tagged the Question as C++11 (so we cannot rely on type deduction for the return type), so here is a solution that returns a const_iterator (others is const):

#include <unordered_set>

class Foo;

namespace std
{
  template <>
  struct hash<Foo>
  {
    size_t operator()(Foo const & bar) const;
  };
}

class Foo
{
public:
  template <class Hash>
  typename std::unordered_set<Foo, Hash>::const_iterator findClosest(std::unordered_set<Foo, Hash> const &others)
  {
    return std::end(others);
  }

  size_t hashValue() const {
    return std::hash<int>()(m_Member);
  }

private:
  int m_Member;
};

size_t std::hash<Foo>::operator()(Foo const & bar) const
{
    return bar.hashValue();
}

int main()
{
    Foo f;
    std::unordered_set<Foo> fs;
    f.findClosest(fs);
}

You might find the code is clearer and easier to maintain if you move the concerns of hashing Foo and finding closest distance into free functions.

Here's an example using boost::hash protocols (which I find to be very helpful)

#include <unordered_set>
#include <boost/functional/hash.hpp>

class Foo
{
public:

    // return a tuple of immutable values which should be hashed in order to
    // compute this object's hash value
    auto hashy_stuff() const // -> std::tuple<const int&> // if you are < c++14
    {
        return std::tie(m_Member);
    }

private:
  int m_Member;
};

// implement the boost::hash hash_value protocol in terms of hashy_stuff
// (we win because now it's DRY and works with all boost hash functions)
auto hash_value(Foo const& foo) -> std::size_t { return boost::hash_value(foo.hashy_stuff()); }

// implement std::hash in terms of boost::hash (again, DRY because it automatically
// uses hash_value())
namespace std { template<> struct hash<Foo> : ::boost::hash<Foo> {}; }

// implement findClosest as a free function. Its concerns are tangential to
// the Foo-ness of a Foo.
std::unordered_set<Foo>::const_iterator 
findClosest(Foo const& /*foo*/, std::unordered_set<Foo> const &others)
{
    // implementation here in terms of public interface
    return std::end(others);
}

extern auto make_foos() -> std::unordered_set<Foo>;
extern auto make_foo() -> Foo;

bool test()
{
    auto foos = make_foos();
    auto foo = make_foo();

    auto i = findClosest(foo, foos);

    return i != end(foos);
}

Instead of specializing template in std namespace you can try below method if you like that.

#include <iostream>
#include <unordered_set>
struct FooHash;
class Foo {
    public:
    Foo(){
    }
    ~Foo(){
    }
    friend FooHash;
    private:
    std::string m_str;
};
struct FooHash
{
    std::size_t operator()(Foo const& f) const noexcept{
        return std::hash<std::string>{}(f.m_str);
    }
};
int main(){
    std::unordered_set<Foo,FooHash> fSet;
    return 0;
}

Live Code

Related