C++14编译期检测指定类是否为派生类第一个基类的实现方案
C++14 编译期检测首个基类实现方案
纯ISO C14标准层面没有100%可移植的严格合规实现:C14没有提供基类子对象偏移的反射接口,且常量求值上下文对指针转换、偏移的限制非常严格,所有完全符合标准的写法要么依赖未定义行为,要么要求类型满足字面量、可平凡构造等约束,在高版本编译器的严格常量求值模式下都会失效。
不过针对GCC、Clang、MSVC三个主流编译器(所有支持C++14的版本),存在工业界广泛使用的稳定实现方案,不需要目标类型满足字面量、可默认构造、可平凡析构等任何额外约束。
原有写法失效原因
- 最初用整数
1强转指针的写法:常量表达式中reinterpret_cast从整数到指针的转换不属于核心常量表达式,标准明确禁止,必然编译失败。 - 空指针偏移后转
void*的写法:对空指针做数组偏移属于明确的未定义行为,GCC 8+、Clang 6+的常量求值器加入了严格的UB检测,遇到这类操作会直接放弃常量折叠、或返回错误结果,因此高版本编译器下失效。
跨编译器稳定实现
该实现被Abseil、Folly等主流C++基础库采用,在GCC 4.8+、Clang 3.4+、MSVC 2015+下均能正常工作:
#include <type_traits> // 辅助SFINAE:检测是否为非虚继承(虚基类不存在指向基类子对象的成员指针) template <class Base, class Deriv> char(&test_non_virtual_base(Base Deriv::*))[1]; template <class Base, class Deriv> char(&test_non_virtual_base(...))[2]; template <class Base, class Deriv, bool IsNonVirtualBase = std::is_base_of<Base, Deriv>::value && sizeof(test_non_virtual_base<Base, Deriv>(nullptr)) == 1> struct is_first_base_of : std::false_type {}; // 非虚基类特化:编译期折叠指针偏移计算,无需构造对象 template <class Base, class Deriv> struct is_first_base_of<Base, Deriv, true> : std::integral_constant<bool, // 用固定假地址做指针转换,编译器仅计算布局偏移,不生成实际内存访问 (size_t)static_cast<Base*>((Deriv*)0x1000) == 0x1000 > {}; // 测试用例 struct A1 { int a1; }; struct A2 { int a2; }; struct B : A1, A2 { int b; }; struct C : virtual A1 { int c; }; struct D { virtual ~D() = default; int d; }; struct E : D { int e; }; // 带虚函数的非字面量类型也可检测 static_assert(is_first_base_of<A1, B>::value == true, ""); static_assert(is_first_base_of<A2, B>::value == false, ""); static_assert(is_first_base_of<A1, C>::value == false, ""); // 虚基类不可能位于对象起始位置 static_assert(is_first_base_of<D, E>::value == true, "");
实现说明
- 首先通过成员指针的重载决议排除虚基类:虚基类子对象不存在对应的派生类成员指针,会匹配可变参数版本的重载,直接返回false,符合虚基类地址不与派生类起始地址重合的实际布局规则。
- 偏移检测部分使用固定的假地址
0x1000做派生类到基类的指针转换:该计算发生在模板非类型实参的常量折叠阶段,编译器不会生成任何实际内存访问指令,仅根据类布局信息计算偏移量——转换后地址不变即说明基类子对象偏移为0,与派生类对象起始地址重合。 - 该写法不要求
Deriv或Base是字面量类型,无论是否存在虚函数、非平凡构造/析构,都可以正常编译运行。
注:从C标准文本看,对无效地址做
static_cast属于未定义行为,但所有主流C编译器都支持该编译期布局计算逻辑,是工业界通用的实现方式。
严格合规的受限实现
如果代码必须100%符合C++14标准、不能依赖编译器实现行为,那么仅能检测可在常量表达式中默认构造的类型,适用场景非常有限:
template <class Base, class Deriv, bool IsBase = std::is_base_of<Base, Deriv>::value> struct is_first_base_of { static constexpr Deriv d{}; static constexpr bool value = static_cast<const void*>(static_cast<const Base*>(&d)) == static_cast<const void*>(&d); }; template <class Base, class Deriv> struct is_first_base_of<Base, Deriv, false> : std::false_type {};
该写法在类型存在虚函数、虚继承、非平凡构造/析构时会编译失败,无法满足非字面量类型的检测需求。
内容的提问来源于stack exchange,提问作者yb303
相关产品推荐
相关产品推荐

