aviad1
aviad1

Reputation: 354

Checking for template parent class in C++ using SFINAE

I've been learning the concept of SFINAE in C++ recentlly and I am currentlly trying to use it in a project.

The thing is, what I'm trying to do is different than anything I could find, and I can't figure out how to do it.

Let's say I have a template class called MyParent:

template <typename Elem>
class MyParent;

And a non-template class called MyClass, that inherites it, using char as Elem:

class MyClass : public MyParent<char>;

Now, I want to use SFINAE in order to check if a typename inherites MyParent, regardless of what Elem type is used.

I can't use std::is_base_of, because of the parent's template.

I've tried to do the following:

template <typename T>
struct is_my_parent : std::false_type {};
template <typename Elem>
struct is_my_parent<MyParent<Elem>> : std::true_type {};

Now, if I check for is_my_parent<MyParent<Elem>>::value, it gives me true. Which is good. However, when I check for is_my_parent<MyClass>::value, I recive false. Which kind of makes sence because MyClass isn't actually MyParent<Elem>, but I didn't manage to get what I wanted.

Is there any convenient way to achive such a thing in C++, other than defining is_my_parent for each and every class that inherites from MyParent?

Upvotes: 4

Views: 505

Answers (3)

Chris Uzdavinis
Chris Uzdavinis

Reputation: 6131

I like Jarod42's answer much better, but an actual SNINAE approach somewhat close to your attempt can work. Here's what I came up with.

To use the type_traits to answer this, we need to know the type of the element. We can make MyParent expose it:

template <typename Elem>
class MyParent {
public:
    using ElemType = Elem;
};

Then the default (false) is_my_parent takes an extra arg and the void_t technique* can be used:

template <typename T, typename = void>
struct is_my_parent : std::false_type {};

template <typename T>
struct is_my_parent<T, std::void_t<typename T::ElemType>> : 
    std::is_base_of<MyParent<typename T::ElemType>, T>::type {};

The specialization is only valid if ElemType is an accessible type in T, and then it results in std::true|false type if the inheritance relationship holds.

live example: https://godbolt.org/z/na5637Knd

But not only is the function overload resolution a better approach for simplicity and size, it will also compile much faster.

(*) void_t was exposed to the world in this fantastic 2-part 2014 talk by Walter Brown. Recommended even if only for review. https://www.youtube.com/watch?v=Am2is2QCvxY

Upvotes: 0

Is there any convenient way to achive such a thing in C++, other than defining is_my_parent for each and every class that inherites from MyParent?

There is, but you'll need to use more elaborate meta-programming techniques. Go entirely back to basics, as it were.

template <class C>
class is_my_parent {
    using yes = char;
    using no  = char[2];
    
    template<typename t>
    static yes& check(MyParent<t> const*);

    static no& check(...);

public:
    enum { value = (1 == sizeof check(static_cast<C*>(0))) };
};

It relies on two basic properties of function overloading and templates:

  1. A derived class can be used to match a function template that takes a base class template as an argument.
  2. Ellipsis offer a conversion sequence that is always considered worse than any other.

Then it's just a matter of inspecting the return type of the chosen overload to determine what we got. Other than the type alias, you can even use this in C++03. Or you can modernize it, so long as overload resolution does the work for you, the check will be performed just the same.

Upvotes: 3

Jarod42
Jarod42

Reputation: 217810

You might do

template <typename T>
std::true_type is_my_parent_impl(const MyParent<T>*);

std::false_type is_my_parent_impl(const void*);

template <typename T>
using is_my_parent = decltype(is_my_parent_impl(std::declval<T*>()));

Demo

Upvotes: 8

Related Questions