是否可以在编译时检查类型是否派生自模板的某个实例化

Is it possible to check at compile time whether a type is derived from some instantiation of a template?

本文关键字:是否 实例化 类型 编译 检查 派生      更新时间:2023-10-16

我想编写一个模板函数,如果传递的类型派生自另一个类的任何模板实例化,则该函数的行为方式为一种方式,如果不是,则以另一种方式运行。

我认为下面的代码捕捉了我想做的事情。 不幸的是,呼叫者为doubleDerived打印"通用"。

#include <iostream>
template <typename T>
struct Base
{
};
struct Derived
:
    public Base<int>
{
};
template <typename T>
void Foo(const T&)
{
    std::cout << "generic" << std::endl;
}
template <typename T>
void Foo(const Base<T>&)
{
    std::cout << "derives from Base<T>" << std::endl;
}
template <typename T>
void Caller(const T& t)
{
    Foo(t);
}
int main()
{
    double x;
    Caller(x);
    Derived d;
    Caller(d);
    return 0;
}

(请注意,Caller 不知道其参数可能派生自Base的哪个实例化。

它调用const T&重载,因为它比const base<T>&匹配更好。原因是调用第一个不需要转换,而第二个需要派生到基数的转换。

这里有一个快速的技巧,向您展示如何完成它(请注意引入的基类(:

#include <iostream>
#include <type_traits>
struct EvenMoreBase {};
template <typename T>
struct Base : EvenMoreBase
{
};
struct Derived
:
    public Base<int>
{
};
template <typename T>
typename std::enable_if<!std::is_base_of<EvenMoreBase, T>::value>::type
Foo(const T&)
{
    std::cout << "generic" << std::endl;
}
template <typename T>
void Foo(const Base<T>&)
{
    std::cout << "derives from Base<T>" << std::endl;
}
template <typename T>
void Caller(const T& t)
{
    Foo(t);
}
int main()
{
    double x;
    Caller(x);
    Derived d;
    Caller(d);
    return 0;
}

如果您能够使用 C++11(或一般<type_traits>(,以下也是一个可能的解决方案,不仅涵盖 T : Base<T> 类型,即 CRTP 的实例,还涵盖没有其他基类的T : Base<U>,如您的示例中要求的那样。

#include <iostream>
#include <type_traits>
template <typename T>
struct Base
{
  typedef T base_value_type;
};
struct Derived : public Base<int>
{
};
template <typename T, typename = T>
struct IsDerived
{
  static const bool value = false;
};
template <typename T>
struct IsDerived<T, typename std::enable_if<std::is_base_of<Base<typename T::base_value_type>, T>::value, T>::type>
{
  static const bool value = true;
};

template <typename T>
void Caller(const T&)
{
  std::cout << IsDerived<T>::value << std::endl;
}
int main()
{
  Caller(double());  // false
  Caller(Derived()); // true
  return 0;
}

请注意typedef T base_value_type - 可以随便叫。这个想法是,从Base<U>派生的每个类型T都可以利用基础模板参数的知识。T == U与否并不重要。一旦传入没有typedef T base_value_typeT,尝试替换第二个参数将失败,因此不会生成此特定T的专用化。

编辑:处理完您的评论后,并受到我发布的线程的启发,我试图在检查某些时间类型T : Base<U>时以某种方式提取一些基本参数U。我认为这不能以您想要的方式完成,即您传递任何T并提取U.但是,您可以做两件事。

简单的解决方案:如果您可以控制派生类的实现方式,则无需在基类中添加typedef,只需在派生类中添加相应的 typedef:

template <typename BaseParamType>
class Derived : public Base<BaseParamType>
{
public:
  typedef BaseParamType base_param_type;
}

或者,如果您不希望派生类也是类模板,只需将类型直接硬编码到类型中(您已经知道 base 参数的类型(:

class Derived : public Base<int>
{
public:
  typedef int base_param_type;
}

更复杂的解决方案:至少对于可能的U预期子集,您可以执行以下操作:

template <typename DerivedType,
          typename BaseParamType = DerivedType,
          bool   = std::is_base_of<Base<BaseParamType>, DerivedType>::value>
struct Extract
{
  typedef BaseParamType type;
};
template <typename T, typename U>
struct Extract<T, U, false>;
int main()
{
  Extract<DerivedCRTP>::type;     // CRTP - trivial
  Extract<Derived, int>::type;    // type == int, Derived is derived from Base<int>
  Extract<Derived, double>::type; // compile-time error, undefined template
  return 0;
}

这并不像将某个类型的实例传递给推导模板函数并神奇地拥有它那么方便,但您至少可以测试某些类型T是否派生自Base<U>,如果不是,则会出现编译时错误。

由于基类必须是具体的类(而不是模板(,因此无法知道它是模板类还是非模板类。

换句话说:

struct A1 : public B1
{};
struct A2 : public B2<int>
{};

在这两种情况下,两个基类都是具体类型。