简体   繁体   中英

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?

This answer has an interesting approach to finding if a type exists using SFINAE.

Adapted to check if a type T::Purr exists, it allows you to write the type trait without the problematic specialization.

#include <type_traits>

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

// A specialization without Purr, to test
template <>
struct Kitty<5>{ };

// has_purr is taken and adapted from https://stackoverflow.com/a/10722840/7359094
template<typename T>
struct has_purr
{   
    template <typename A> 
    static std::true_type has_dtor(decltype(std::declval<typename A::Purr>().~Purr())*);

    template<typename A>
    static std::false_type has_dtor(...);

    typedef decltype(has_dtor<T>(0)) type;

    static constexpr bool value = type::value;
};

// Check if a type is an instance of Kitty<T>
template<typename T>
struct is_kitty : std::false_type {};
template<unsigned T>
struct is_kitty<Kitty<T>> : std::true_type {};

template <typename T> 
struct Check : std::bool_constant< is_kitty<T>::value && has_purr<T>::value> {};

static_assert( Check<int>::value == false, "int doesn't have purr" );
static_assert( Check<Kitty<0>>::value == true, "Kitty<0> has purr" );
static_assert( Check<Kitty<5>>::value == false, "Kitty<5> doesn't has purr" );

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM