Search code examples
c++templatesc++20template-specializationpartial-specialization

Template specialization for the base template type for future derived types


I have a class that works as wrapper for some primitives or custom types. I want to write explicit specialization for custom template type. My code that reproduces the problem:


template < class T >
struct A {
    void func() { std::cout << "base\n"; }
};

template <>
struct A<int> {};

template < class T, class CRTP >
struct BaseCrtp {
    void someFunc() {
        CRTP::someStaticFunc();
    }
};

struct DerrType : BaseCrtp<int, DerrType> {
    static void someStaticFunc() {}
};

template < class T, class CRTP >
struct A< BaseCrtp<T, CRTP> > {
    void func() { std::cout << "sometype\n"; }
};

int main() {
    A<DerrType> a;
    a.func(); // print: "base". should be: "sometype"

    return 0;
}

A<DerrType> use default function, not a specialization. How can I make specialization for these set of classes? I will have a lot of types like DerrType, and I want to make common behavior for all of them. DerrType and others will be used as curiously recurring template pattern


Solution

  • Not sure I fully understood what you want, but maybe something like this:

    template<typename T>
    concept DerivedFromBaseCrtp = requires(T& t) {
        []<typename U, typename CRTP>(BaseCrtp<U, CRTP>&){}(t);
    };
    
    template < DerivedFromBaseCrtp T >
    struct A<T> {
        void func() { std::cout << "sometype\n"; }
    };
    

    The concept basically checks whether T is equal to or is publicly inherited (directly or indirectly) from some specialization of BaseCrtp. Otherwise the call to the lambda would be ill-formed. Template argument deduction only succeeds in the call if the argument and parameter type match exactly or the argument has a derived type of the parameter. If the class is inherited non-publicly, the reference in the call can't bind to the parameter.

    The concept will however fail if the type is inherited from multiple BaseCrtp specializations, in which case template argument deduction on the call will not be able to choose between the multiple choices.


    Alternatively you can also use the stricter concept

    template<typename T>
    concept CrtpDerivedFromBaseCrtp = requires(T& t) {
        []<typename U>(BaseCrtp<U, T>&){}(t);
    };
    

    which will also require that the type T is actually using the CRTP pattern on BaseCrtp (directly or through a some base class between BaseCrtp and T). Again, this will fail if T is inherited multiple times from some BaseCrtp<U, T> specialization, although it will ignore specializations with a type other than T in the second position.


    For another alternative you might want to check that T is derived from some type X such that X is derived from BaseCrtp<U, X> for some U (meaning that X uses the CRTP pattern correctly). That could be done using this variation:

    template <typename T>
    concept CrtpDerivedFromBaseCrtp =
        requires(T& t) {
            []<typename U, typename CRTP>(BaseCrtp<U, CRTP>&)
                requires(std::is_base_of_v<CRTP, T> &&
                         std::is_base_of_v<BaseCrtp<U, CRTP>, CRTP>)
            {}
            (t);
        };
    

    Again, this fails if T is derived from multiple BaseCrtp specializations, directly or indirectly.