派生类会继承基类的接口,所以我们经常会把一个基类派生出的多个派生类当作同一个类别。在某些函数或者类中,我们可能希望只支持某一类别的输入,即只支持某个类型或者这个类型的派生类,此时就需要判断输入类型是否继承自目标类型。
考虑这样一种情形:线条(Line)由一系列点构成,而“点”可以有多种类型:圆点(Dot)、星点(Star)…,另外还有一种类型“平面”(Plane)则不是一种“点”,它们定义如下:
class Point { }; class Dot : public Point { }; class Star : public Point { }; class Plane { }; template <typename Pt> class Line { };显然,线条可以由圆点或星点构成,但不能由平面构成,我们希望 Line 的模板参数 Pt 是一种 Point,如何在代码中进行判断呢?
C++11 提供了std::is_base_of(头文件<type_traits>)用于判断两个类型的继承关系,参考cppreference,使用方法为:
std::is_base_of<A, B>::value; // get true if B is derived from A所以上述问题中,Line可以这么定义:
template <typename Pt> class Line { static_assert(std::is_base_of<Point, Pt>::value, "template params MUST be Point!"); };如果用户使用了错误的类型作为模板参数,比如 Line<Plane> line;,则会在编译时报错。
点的坐标通常可以是多种类型的(int、float、double),下面我们给Point加上模板参数Scalar来支持不同类型数据:
template <typename Scalar> class Point { }; template <typename Scalar> class Dot : public Point<Scalar> { }; template <typename Scalar> class Star : public Point<Scalar> { }; template <typename Scalar> class Plane { };这时候你会发现std::is_base_of就不那么好用了,它会要求你提供Point和Pt的模板参数(即各自的Scalar),而且它会认为Scalar不同的两种 Point 没有继承关系!这往往并不是我们所希望的:
std::is_base_of<Point<float>, Dot<float>>::value; // true std::is_base_of<Point<float>, Dot<double>>::value; // false针对这种情况,我在stackoverflow@Jarod42的回答中找到了如下方法:
// case1: T* can cast to C* template <template <typename...> class C, typename...Ts> std::true_type is_base_of_template_impl(const C<Ts...>*); // case2: T* cannot cast to C* template <template <typename...> class C> std::false_type is_base_of_template_impl(...); template <template <typename...> class C, typename T> using is_base_of_template = decltype(is_base_of_template_impl<C>(std::declval<T*>()));其中std::declval<T*>()是在不构造T*指针的情况下获得一个T*指针的引用类型,仅用于类型推断。这个方法利用的是派生类指针能够隐式转换为基类指针,基类C的模板参数则被参数包Ts...捕获,因此支持任意数量的模板参数,而其缺陷是仅适用于 public 继承(protected 或者 private 继承的类指针无法转化为基类指针)。使用方法与std::is_base_of类似:
is_base_of_template<Point, Dot<float>>::value; // true is_base_of_template<Point, Dot<double>>::value; // true is_base_of_template<Point, Plane<double>>::value; // falsestackoverflow同一问题下@jenka 的回答提供了适用于 protected 继承的解决方法(其代码好像有点问题,@Anton Dyachenko的补充后才正常)。这个方法能用,但原理我目前还没看明白,先贴出代码吧:
template <template <typename...> class BaseTemplate, typename Derived, typename TCheck = void> struct test_base_template; template <template <typename...> class BaseTemplate, typename Derived> using is_base_of_template = typename test_base_template<BaseTemplate, Derived>::is_base; //Derive - is a class. Let inherit from Derive, so it can cast to its protected parents template <template <typename...> class BaseTemplate, typename Derived> struct test_base_template<BaseTemplate, Derived, std::enable_if_t<std::is_class_v<Derived>>> : Derived { template <typename... T> static constexpr std::true_type test(BaseTemplate<T...>*); static constexpr std::false_type test(...); using is_base = decltype(test((Derived*)nullptr)); }; //Derive - is not a class, so it is always false_type template <template <typename...> class BaseTemplate, typename Derived> struct test_base_template<BaseTemplate, Derived, std::enable_if_t<!std::is_class_v<Derived>>> { using is_base = std::false_type; };