Conditionally specialize std::hash for std::shared_ptr struct

554 Views Asked by At

I have a base Base class. The goal is to force specialize std::hash for std::shared_ptr with all the classes that inherit from Base.

I have tried the following approach with a dummy template parameter but the compiler error obviously complains that this is a redefinition of struct std::hash<std::shared_ptr<_Tp>>.

class Base {
};

class Derived : public Base {
};

namespace std {

template<typename T,
         typename std::enable_if<std::is_base_of<Base, T>::value, Base>::type* = nullptr
>
struct hash<std::shared_ptr<T>> {    
    size_t operator()(const std::shared_ptr<T>& d) const {
        return 616;
    }
};
}

My question is: is it possible to conditionally specialize an std class like this?

1

There are 1 best solutions below

1
On BEST ANSWER

The problem with the code in your question is not that it is a redefinition, but that default template arguments are not allowed in partial specializations and that all template parameters in a partial specialization must be deducible.

std::hash does not provide a second template parameter in the primary template that could be used for SFINAE, but based on this answer you could do something like this as a workaround:

#include <memory>
#include <utility>

class Base {};

class Derived : public Base {};

template <typename First, typename... Others>
using first = First;

namespace std {

template <typename T>
struct hash<first<std::shared_ptr<T>,
                  std::enable_if_t<std::is_base_of_v<Base, T>>>> {
  size_t operator()(const std::shared_ptr<T>& d) const { return 616; }
};

}  // namespace std

which I would assume is ok in principle, because the declaration depends a user-defined type Base.

There is an unresolved issue in the standard regarding whether or not this specialization should be considered a redefinition of the standard's specialization for std::shared_ptr. (GCC thinks it isn't, Clang thinks it is.)

But more importantly, then you still have the problem that this partial specialization is not more specialized than the one that the standard provides for std::shared_ptr. Therefore any actual use would result in an ambiguity error and I don't think there is any way to make the specialization more specialized.

Thus I think your only solution is to define an explicit specialization of std::hash for each derived type, maybe with the help of a macro. Or alternatively (and probably more appropriately) you should write your own hasher functor and provide that as alternative to std::hash where it is needed.