Search code examples
c++derived-classpartial-specializationcrtp

How to partially specialize a class template for all derived types?


I want to partially specialize an existing template that I cannot change (std::tr1::hash) for a base class and all derived classes. The reason is that I'm using the curiously-recurring template pattern for polymorphism, and the hash function is implemented in the CRTP base class. If I only want to partially specialize for a the CRTP base class, then it's easy, I can just write:


namespace std { namespace tr1 {

template <typename Derived>
struct hash<CRTPBase<Derived> >
{
    size_t operator()(const CRTPBase<Derived> & base) const 
    { 
        return base.hash(); 
    }
};

} }

But this specialization doesn't match actual derived classes, only CRTPBase<Derived>. What I want is a way of writing a partial specialization for Derived if and only if it derives from CRTPBase<Derived>. My pseudo-code is


namespace std { namespace tr1 {

template <typename Derived>
struct hash<typename boost::enable_if<std::tr1::is_base_of<CRTPBase<Derived>, Derived>,
    Derived>::type>
{
    size_t operator()(const CRTPBase<Derived> & base) const 
    { 
        return base.hash(); 
    }
};

} }

...but that doesn't work because the compiler can't tell that enable_if<condition, Derived>::type is Derived. If I could change std::tr1::hash, I'd just add another dummy template parameter to use boost::enable_if, as recommended by the enable_if documentation, but that's obviously not a very good solution. Is there a way around this problem? Do I have to specify a custom hash template on every unordered_set or unordered_map I create, or fully specialize hash for every derived class?


Solution

  • There are two variants in the following code. You could choose more appropriated for you.

    
    template <typename Derived>
    struct CRTPBase
    {
        size_t hash() const {return 0; }
    };
    
    // First case 
    //
    // Help classes
    struct DummyF1 {};
    struct DummyF2 {};
    struct DummyF3 {};
    template<typename T> struct X; 
    
    // Main classes
    template<> struct X<DummyF1> : CRTPBase< X<DummyF1> > {
        int a1;
    };
    
    template<> struct X<DummyF2> : CRTPBase< X<DummyF2> > {
        int b1;
    };
    
    // typedefs
    typedef X<DummyF1> F1;
    typedef X<DummyF2> F2;
    typedef DummyF3    F3; // Does not work
    
    namespace std { namespace tr1 {
        template<class T>
        struct hash< X<T> > {
            size_t operator()(const CRTPBase< X<T> > & base) const     
            {         
                return base.hash();     
            }
        };
    }} // namespace tr1 // namespace std 
    
    //
    
    // Second case
    struct DummyS1 : CRTPBase <DummyS1> {
        int m1;
    };
    //
    template<typename T> 
    struct Y : T {};
    //
    typedef Y<DummyS1> S1;
    
    
    namespace std { namespace tr1 {
        template<class T>
        struct hash< Y<T> > {
            size_t operator()(const CRTPBase<T> & base) const     
            {         
                return base.hash();     
            }
        };
    }} // namespace tr1 // namespace std 
    
    void main1()
    {
        using std::tr1::hash;
        F1 f1;
        F2 f2;
        F3 f3;
        hash<F1> hf1; size_t v1 = hf1(f1); // custom hash functor
        hash<F2> hf2; size_t v2 = hf2(f2); // custom hash functor
        hash<F3> hf3; size_t v3 = hf3(f3); // error: standard hash functor
    
        S1 s1;
        hash<S1> hs1; size_t w1 = hs1(s1); // custom hash functor
    
    }