I would like to define a C++ template specialization that applies to all subclasses of a given base class. Is this possible?
In particular, I'd like to do this for STL's hash<>. hash<> is defined as an empty parametrized template, and a family of specializations for specific types:
template<class _Key>
struct hash { };
template<>
struct hash<char>
{
size_t
operator()(char __x) const
{ return __x; }
};
template<>
struct hash<int>
{
size_t
operator()(int __x) const
{ return __x; }
};
...
I would like to define something like this:
template<class Base>
struct hash {
size_t operator()(const B开发者_开发知识库ase& b) const {
return b.my_hash();
}
};
class Sub : public Base {
public:
size_t my_hash() const { ... }
};
and be able to use it like this:
hash_multiset<Sub> set_of_sub;
set_of_sub.insert(sub);
However, my hash template conflicts with the generic one from STL. Is there a way (perhaps using traits) to define a template specialization that applies to all subclasses of a given base class (without modifying the STL definitions)?
Note that I know I can do this with some extra template parameters whenever this hash specialization is needed, but I'd like to avoid this if possible:
template<>
struct hash<Base> {
size_t operator()(const Base& b) const {
return b.my_hash();
}
};
....
// similar specialization of equal_to is needed here... I'm glossing over that...
hash_multiset<Sub, hash<Base>, equal_to<Base> > set_of_sub;
set_of_sub.insert(sub);
Since C++ 11 you can use SFINAE together with standard library enable_if and is_base_of to solve the problem.
C++20 makes a cleaner solution possible - basically equivalent to enable_if, which even (optionally) works with CRTP
#include <concepts>
#include <functional>
#include <unordered_set> // just for demo in main()
template <class T>
class Base {};
class Derived final : public Base<Derived> {};
template<class T>
requires std::derived_from<T, Base<T>>
struct std::hash<T> {
// constexpr is optional
constexpr size_t operator() (const T& value) const noexcept {
return 0xDEADBEEF; // FIXME: do something better :)
}
};
int main() {
// If operator() weren't constexpr, this couldn't be a *static* assert
static_assert(std::hash<Derived>()(Derived {}) == 0xDEADBEEF);
std::unordered_set<Derived> usageTest;
return 0;
}
The solution is to use SFINAE to decide whether or not to allow your specialisation depending on the class inheritance structure. In Boost you can use enable_if
and is_base_of
to implement this.
- http://www.boost.org/doc/libs/1_47_0/libs/utility/enable_if.html
- http://www.boost.org/doc/libs/1_47_0/libs/type_traits/doc/html/boost_typetraits/reference/is_base_of.html
This was the best I could do:
template<>
struct hash<Sub> : hash<Base> {
};
I'm a little worried that I didn't have to make operator()
virtual, though.
I don't think it is possible, because the way to do template specialization based on something more complex than just matching the type is C++ SFINAE, which requires second (dummy) template argument. Unfortunatelly, std::hash
takes only one template argument and it is not allowed to create another version of std::hash
with two template arguments. Therefore, the if you aren't satisfied with Jayen's solution, you can create your own hash
type:
#include <iostream>
#include <type_traits>
using namespace std;
class ParentClass {};
class ChildClass : public ParentClass {};
// SFINAE, if T is not a child of ParentClass, substitution will fail
// You can create specializations for the general case, for another base classes, etc.
template<typename T, typename=typename enable_if<is_base_of<ParentClass, T>::value, T>::type>
struct your_hash
{
size_t operator()(const T& value)
{
return 42;
}
};
int main()
{
ParentClass pc;
ChildClass cc;
cout<<your_hash<ParentClass>()(pc)<<"\n";
cout<<your_hash<ChildClass>()(cc)<<"\n";
}
精彩评论