Search code examples
c++templatespartial-specialization

How to specialize a class for an inner type of a template?


I have a class that acts as a type trait, returning whether a certain condition is true. It's intended to mark classes as supporting a particular feature.

template <typename T> struct Check : std::false_type { };

I have a template class that contains an inner class:

template <unsigned N>
struct Kitty
{
  struct Purr;
};

I want to mark the inner class Purr as supporting the feature denoted as Check. In other words, I want to make it so that Check<Kitty<123>::Purr>::value is true. I tried doing the following, but I get an error:

template <unsigned X>
struct Check<typename Kitty<X>::Purr> : std::true_type { };

error: template parameters not deducible in partial specialization:

Is it possible to accomplish this, or is it a limitation of C++ that you can't specialize on inner template class members?


Solution

  • As outlined in my comment, it is possible to make this a deduced context by using a base class, which I'll call KittyBase. Using a base class is actually common for templates, to avoid having unnecessary code duplicated for every new instantiation. We can use the same technique to get Purr without needing to deduce N.

    However, simply putting Purr in the base class will remove its access to N. Fortunately, even in making Purr itself a template, this can still be a non-deduced context: Live example

    #include <type_traits>
    
    template <typename T> struct Check : std::false_type { };
    
    struct KittyBase 
    {    
        template<unsigned N> // Template if Purr needs N.
        struct Purr;
    
    protected:
        ~KittyBase() = default; // Protects against invalid polymorphism.
    };
    
    template <unsigned N>
    struct Kitty : private KittyBase
    {
        using Purr = KittyBase::Purr<N>; // Convenience if Purr needs N.
        Purr* meow;
    };
    
    template <unsigned X>
    struct Check<typename KittyBase::Purr<X>> : std::true_type { };
    
    static_assert(not Check<int>{});
    static_assert(Check<Kitty<123>::Purr>{});
    static_assert(Check<Kitty<0>::Purr>{});
    
    int main() {}
    

    If you wish, you can even make KittyBase::Purr private and use template<typename T> friend struct Check; to grant access to the trait. Unfortunately, I don't know whether you can limit that to only certain specializations of the trait.