0

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?

Jonas
  • 6,915
  • 8
  • 35
  • 53
Myria
  • 3,372
  • 1
  • 24
  • 42
  • No, the error is being explicit about what the problem is. That is a limitation, not what you suggest. Something like `struct Check::Purr> : true_type { };` is fine. – DeiDei Jan 19 '17 at 21:43
  • Possible duplicate of [Why is the template argument deduction not working here?](http://stackoverflow.com/questions/1268504/why-is-the-template-argument-deduction-not-working-here) – LogicStuff Jan 19 '17 at 21:43
  • 1
    It's evident that you mean for every specialization of `Kitty::Purr` to be covered. But consider what happens if a specialization of `Kitty` does not have `Purr` (`template<> struct Kitty<0> {};`). The language solves this by saying you simply can't do that. In this case, it would be nice to simply ignore that specialization, or to somehow say that every specialization must conform to that, but alas. What you could potentially do is make a base class `struct KittyBase { struct Purr; };` and have `Kitty` inherit from it. Need `Purr` to access `N`? Make `Purr` a template in the base. – chris Jan 19 '17 at 22:02

2 Answers2

1

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" );
Community
  • 1
  • 1
François Andrieux
  • 28,148
  • 6
  • 56
  • 87
1

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.

chris
  • 60,560
  • 13
  • 143
  • 205