模板类作用域内定义递归variant为何报不完全类型错误?
递归std::variant在模板类作用域的编译问题
我尝试将递归variant的盒子方案适配到递归variant需定义在模板类作用域的场景中,简化后得到如下代码片段:
#include <variant> struct LiteralExpr { int value; }; template <typename T> struct BaseSpace { struct AddExpr; struct AddExprBox { const AddExpr &_impl; public: AddExprBox(const AddExpr &obj) {} }; using Expr = std::variant<AddExprBox, LiteralExpr>; struct AddExpr { std::variant<AddExprBox, LiteralExpr> lhs; }; }; auto main() -> int { auto expr = BaseSpace<int>::Expr(LiteralExpr{2}); }
这段代码在clang下编译失败,报错信息如下:
main.cpp:22:47: error: field has incomplete type 'std::variant<AddExprBox, LiteralExpr>'
std::variant<AddExprBox, LiteralExpr> lhs;
存在以下几种可成功编译的情况:
- 移除
AddExprBox中的拷贝构造函数,编译通过; - 将BaseSpace改为非模板类,编译通过;
- 显式实例化BaseSpace,编译通过;
问题原因
这是模板类实例化规则与std::variant的完整类型要求共同作用的结果:
- 模板类
BaseSpace<T>定义时,编译器处理AddExpr内的std::variant成员时,需要确定该类型的大小和布局,这要求模板参数必须是完整类型。 AddExprBox自定义了拷贝构造函数,会触发编译器对AddExprBox的完整类型检查——但此时AddExpr仅为前向声明的不完整类型,AddExprBox依赖于AddExpr,间接导致std::variant的模板参数不满足完整类型要求。- 非模板类场景下,编译器会延迟类型完整性检查到整个类定义完成后;显式实例化会强制编译器提前处理完整类定义;移除自定义构造函数时,
AddExprBox的默认操作不会触发提前的完整类型检查,因此都能通过编译。
解决方法
方法一:延迟AddExprBox构造函数的定义
将构造函数移到类外,在AddExpr完整定义后实现,避免提前触发类型检查:
#include <variant> struct LiteralExpr { int value; }; template <typename T> struct BaseSpace { struct AddExpr; struct AddExprBox { const AddExpr &_impl; public: AddExprBox(const AddExpr &obj); }; using Expr = std::variant<AddExprBox, LiteralExpr>; struct AddExpr { Expr lhs; }; }; // 在AddExpr完整定义后实现构造函数 template <typename T> BaseSpace<T>::AddExprBox::AddExprBox(const AddExpr &obj) : _impl(obj) {} auto main() -> int { auto expr = BaseSpace<int>::Expr(LiteralExpr{2}); }
方法二:替换直接引用为包装类型(可选)
如果业务场景允许,将AddExprBox中的直接引用改为std::reference_wrapper或智能指针,消除对不完整类型的直接依赖:
#include <variant> #include <functional> struct LiteralExpr { int value; }; template <typename T> struct BaseSpace { struct AddExpr; struct AddExprBox { std::reference_wrapper<const AddExpr> _impl; public: AddExprBox(const AddExpr &obj) : _impl(obj) {} }; using Expr = std::variant<AddExprBox, LiteralExpr>; struct AddExpr { Expr lhs; }; }; auto main() -> int { auto expr = BaseSpace<int>::Expr(LiteralExpr{2}); }
内容的提问来源于stack exchange,提问作者rudolf_markov
相关产品推荐
相关产品推荐

