C++23中指针可互转换类型的类型双关是否不受严格别名规则限制?
原代码的合法性分析
你的原代码存在未定义行为,核心问题在于违反了严格别名规则:
严格别名规则的限制
根据C++23 [basic.lval]/11,只有当glvalue的类型属于特定允许的集合时,才能用它访问对象的存储值。S<true>和S<false>是不同的模板实例化类型,既不属于彼此的动态类型,也不满足「相似类型」的定义(相似类型仅允许cv限定、指针/成员指针/数组的cv差异等,不包含模板参数值不同的实例)。因此,通过reinterpret_cast<S<false>*>(this)生成的S<false>类型glvalue访问S<true>对象的存储,属于未定义行为。指针可互转换的误解
你提到的「传递性」是错误的:C++标准中没有规定指针可互转换关系具有传递性。S<true>与它的x成员是指针可互转换的,S<false>与它的x成员也是,但这并不意味着S<true>和S<false>的对象之间是指针可互转换的——指针可互转换仅适用于同一对象、标准布局类的首成员/基类子对象、同一联合的成员这些直接场景。
布局兼容类型的意义
布局兼容的核心价值并非允许跨类型的指针访问,而是解决以下场景的定义性问题:
- 字节复制的合法性:用
memcpy等函数在两个布局兼容的标准布局类型对象之间复制字节,结果是定义良好的([class.mem.general]/23)。 - 联合的跨成员访问:如果联合的两个成员是布局兼容的标准布局类型,那么活跃其中一个成员后,可以安全读取另一个成员的公共初始序列([class.union]/2)。
- 指针比较的一致性:布局兼容的标准布局类型的指针经
reinterpret_cast后,比较结果与原指针的比较结果一致([expr.reinterpret.cast]/7)。
修改后的代码合法性分析
如果将x移到非模板基类中,代码变为:
#include <type_traits> struct Base { int x; }; template<bool B> struct S : Base { void go() { ++x; if constexpr (B) reinterpret_cast<S<false>*>(this)->go(); } }; static_assert(std::is_standard_layout_v<S<true>> && std::is_standard_layout_v<S<false>>); int main() { S<true>{}.go(); return 0; }
此时代码是合法的,原因如下:
S<true>和S<false>仍为布局兼容的标准布局类,且它们的基类Base是完全相同的类型。- 当通过
reinterpret_cast<S<false>*>(this)访问x时,实际是访问继承自Base的成员,编译器会隐式将S<false>*转换为Base*。根据严格别名规则[basic.lval]/11.6,允许通过基类类型的glvalue访问派生类对象的基类子对象——这里Base是S<true>的基类,glvalue指向的正是对象的Base子对象,完全符合规则要求。
内容的提问来源于stack exchange,提问作者user3188445
相关产品推荐
相关产品推荐

