Reputation: 354
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
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
Reputation: 170163
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:
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