C++中带const参数的std::variant拷贝构造GCC编译报错疑问
问题描述
有一个尚未得到解答的优质问题,关于包含const成员的union:
其中一条建议是使用std::variant作为替代。实际上,const类型的支持是被明确要求的,参见P0086 - Variant设计评审,相关段落说明:
variant<int, const int>variant可以处理const类型:这类类型仅能通过variant构造函数和emplace()方法赋值。
因此我认为variant的拷贝构造函数也应当支持const类型参数。
但我尝试实现该方案的代码:
#include <string> #include <variant> using S = std::variant<const int, const std::string>; int main() { S s(1); S u = s; S v("abc"); S w = v; }
在GCC中编译失败,返回非常长的错误信息,此处仅引用开头部分:
In file included from <source>:2: /opt/compiler-explorer/gcc-trunk-20211024/include/c++/12.0.0/variant: In instantiation of 'constexpr std::__detail::__variant::_Variadic_union<_First, _Rest ...>::_Variadic_union(std::in_place_index_t<_Np>, _Args&& ...) [with long unsigned int _Np = 1; _Args = {const int&}; _First = const std::__cxx11::basic_string<char>; _Rest = {}]': /opt/compiler-explorer/gcc-trunk-20211024/include/c++/12.0.0/variant:409:4: required from 'constexpr std::__detail::__variant::_Variadic_union<_First, _Rest ...>::_Variadic_union(std::in_place_index_t<_Np>, _Args&& ...) [with long unsigned int _Np = 2; _Args = {const int&}; _First = const int; _Rest = {const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >}]' /opt/compiler-explorer/gcc-trunk-20211024/include/c++/12.0.0/bits/stl_construct.h:119:7: required from 'constexpr void std::_Construct(_Tp*, _Args&& ...) [with _Tp = std::__detail::__variant::_Variadic_union<const int, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >; _Args = {const std::in_place_index_t<2>&, const int&}]'
请问是我的代码存在错误,还是GCC本应接受这段代码?
解答
结论
你的代码完全符合C++标准要求,编译失败是旧版本GCC的标准库实现缺陷导致。
原因说明
- C++标准明确规定
std::variant支持const类型作为备选值,仅限制不能对已构造完成的const类型备选值执行赋值操作,拷贝构造属于新对象初始化场景,不在限制范围内。 - 你测试所用的2021年GCC 12开发预览版中,
std::variant的底层union实现逻辑存在问题:拷贝构造时错误尝试对const成员执行赋值操作,而非直接调用拷贝构造函数初始化底层成员,触发了const成员不可修改的编译错误。 - 该缺陷已经在GCC 12正式版及之后的版本中修复,升级到最新稳定版GCC即可正常编译运行这段代码。
旧版本兼容方案
如果暂时无法升级编译器,可以将备选类型声明为非const,仅在访问层增加const约束,实现等效的不可修改效果:
#include <string> #include <variant> using S = std::variant<int, std::string>; // 对外仅暴露const访问接口,保证值不可修改 template<typename T> const T& get(const S& var) { return std::get<T>(var); } int main() { S s(1); S u = s; S v("abc"); S w = v; // 读取操作正常,无法通过返回值修改variant内部存储 int int_val = get<int>(s); const std::string& str_val = get<std::string>(v); }
内容的提问来源于stack exchange,提问作者Fedor
相关产品推荐
相关产品推荐

