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 Foo
does 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;
}
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With