Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

c++11

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;
}
like image 268
Azias Avatar asked Nov 16 '18 08:11

Azias


2 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.

like image 66
Max Langhof Avatar answered Sep 30 '22 14:09

Max Langhof


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);
}
like image 25
Jonas Avatar answered Sep 30 '22 16:09

Jonas