为什么包含带虚函数结构体的std::variant的is_standard_layout返回true
问题核心结论
这不是编译器Bug,是你对std::is_standard_layout的规则、以及标准库std::variant的实现逻辑存在误解,该行为完全符合C++标准要求。
核心前提说明
std::is_standard_layout_v<T>检查的是T类型本身的结构是否符合标准布局要求,和T的模板参数本身是否为标准布局没有直接绑定关系,仅由T实例化后的类成员、类结构决定。- 标准布局和平凡类型是两个完全独立的类型属性:一个类型可以拥有非平凡的构造/析构/复制函数,只要内存布局符合规则,依然属于标准布局类型。
两种测试结果的具体原因
主流标准库(GCC的libstdc++、Clang的libc++)的std::variant会根据备选类型的属性,自动切换两种实现策略,这是你观察到不同结果的直接原因:
情况1:Cancel包含虚函数时,variant为标准布局
当Cancel存在虚函数时,Cancel属于非平凡可销毁、非平凡可复制类型,此时variant会采用通用实现,简化结构如下:
template <typename... Ts> class variant { private: size_t _index; // 标记当前存储的类型索引,为标准布局类型 alignas(/* 所有备选类型最大对齐值 */) char _storage[/* 所有备选类型最大大小 */]; // 存储对象的原始缓冲区,char数组为标准布局类型 // 其余均为成员函数,无额外非静态数据成员 public: // 各类构造、析构、访问接口,包含非平凡的特殊成员函数 };
该实现下的variant本身完全符合标准布局的所有要求:
- 无虚函数、虚基类
- 所有非静态数据成员的访问控制统一(均为private)
- 所有非静态数据成员的类型都是标准布局类型
- 无携带非静态数据成员的基类
因此std::is_standard_layout_v返回true。此时Cancel并没有作为variant的非静态数据成员存在,只是存储在原始缓冲区中,所以Cancel本身是否为标准布局不会影响variant的布局属性。
情况2:移除Cancel的虚函数后,variant为非标准布局
当你删掉Cancel的虚函数后,Cancel变成了平凡可销毁、平凡可复制类型,此时两个备选类型NewOrder、Cancel均满足平凡操作要求,variant会切换到优化的平凡实现,简化结构如下:
template <typename... Ts> class variant { private: size_t _index; union { NewOrder _alt0; Cancel _alt1; } _storage; // 直接用union存储备选类型,支持全平凡操作 };
此时问题出在_storage这个union成员上:标准布局要求类的所有非静态数据成员都必须是标准布局类型,而你的Cancel存在public的j和private的i,访问控制不统一,本身不属于标准布局类型,导致包含它的union也不属于标准布局类型,最终整个variant不符合标准布局要求,所以std::is_standard_layout_v返回false,和你的预期一致。
内容的提问来源于stack exchange,提问作者assassin5615
相关产品推荐
相关产品推荐

