使用 SFINAE 在 C++ 中检查模板 parent class

Checking for template parent class in C++ using SFINAE

我最近一直在用 C++ 学习 SFINAE 的概念,目前我正在尝试在一个项目中使用它。

问题是,我正在尝试做的事情与我能找到的任何事情都不一样,而且我不知道该怎么做。

假设我有一个名为 MyParent 的模板class:

template <typename Elem>
class MyParent;

还有一个名为 MyClass 的 non-template class 继承了它,使用 char 作为 Elem:

class MyClass : public MyParent<char>;

现在,我想使用 SFINAE 来检查类型名称是否继承 MyParent,而不管使用什么 Elem 类型。

由于 parent 的模板,我无法使用 std::is_base_of

我已尝试执行以下操作:

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

现在,如果我检查 is_my_parent<MyParent<Elem>>::value,它会给我 true。哪个好。 但是,当我检查 is_my_parent<MyClass>::value 时,我收到 false。哪一种说得通,因为 MyClass 实际上不是 MyParent<Elem>,但我没能得到我想要的。

除了为继承自 MyParent 的每个 class 定义 is_my_parent 之外,在 C++ 中是否有任何方便的方法来实现这样的事情?

你可能会

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

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?

有,但您需要使用更精细的元编程技术。可以说完全回归基础。

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))) };
};

它依赖于函数重载和模板的两个基本属性:

  1. 派生的 class 可用于匹配以基本 class 模板作为参数的函数模板。
  2. Ellipsis 提供了一个转换序列,该序列总是被认为比其他任何序列都差。

然后只需检查所选重载的 return 类型以确定我们得到了什么。除了类型别名,您甚至可以在 C++03 中使用它。或者您可以对其进行现代化改造,只要重载解析为您完成工作,检查就会以同样的方式执行。

我更喜欢 Jarod42 的回答,但实际的 SNINAE 方法有点接近您的尝试是可行的。这是我想出的。

要使用 type_traits 来回答这个问题,我们需要知道元素的类型。我们可以让 MyParent 公开它:

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

然后默认(假)is_my_parent 需要一个额外的 arg 并且可以使用 void_t 技术*:

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 {};

仅当 ElemType 是 T 中的可访问类型时特化才有效,然后如果继承关系成立,它会导致 std::true|false 类型。

实例:https://godbolt.org/z/na5637Knd

但是,函数重载决议不仅是简化和大小的更好方法,而且编译速度也快得多。

(*) void_t 在 Walter Brown 2014 年精彩的两部分演讲中向全世界曝光。推荐,即使只是为了审查。 https://www.youtube.com/watch?v=Am2is2QCvxY