移动还是复制?主流编译器行为分歧,谁才是正确的?
左值引用/右值复制/移动的包装类跨编译器兼容问题
这是一个用于处理左值和右值的包装类:对左值保留引用,对右值保留副本;右值的处理由特化参数决定是移动还是复制。这类场景常见于右值代理(比如lambda捕获):左值可以通过引用持有,而右值必须被复制或移动,具体行为取决于需要的副本数量。
原始包装类实现
template<class T, bool FWD> struct fwd_or_copy_wrapper { fwd_or_copy_wrapper(T&& t): _t(FWD ? std::forward<T>(t) : std::add_const_t<T>(t)) {} private: T _t; };
推导指南的跨编译器问题
针对该类的推导指南存在兼容性差异:
- GCC无需使用此推导指南即可正常推导模板参数
- MSVC必须依赖它才能完成正确推导
- Clang中此推导指南无法编译
推导指南代码:
template<class T, bool FWD> fwd_or_copy_wrapper(T&& t) -> fwd_or_copy_wrapper<T, FWD>;
行为别名定义
我们通过两个别名特化,分别封装移动和复制行为:
template<class T> using fwd_wrapper = fwd_or_copy_wrapper<T, true>; template<class T> using copy_wrapper = fwd_or_copy_wrapper<T, false>;
各编译器行为差异
- GCC:行为符合预期——绑定左值引用,对右值正确调用复制构造函数或移动构造函数
- MSVC:行为不符合预期——不仅会多余调用复制构造函数,在
fwd_wrapper中还会执行复制而非移动操作
跨编译器兼容修复方案
通过拆分构造函数并使用requires约束,可以解决MSVC的异常行为:
template<class T, bool FWD> struct fwd_or_copy_wrapper { fwd_or_copy_wrapper(T&& t) requires FWD : _t(std::forward<T>(t)) {} fwd_or_copy_wrapper(T&& t) requires (!FWD) : _t(t) {} /* 其余部分不变 */ private: T _t; };
注意:该修复需要使用最新版MSVC编译器(VS 2022 17.11),旧版本会触发另一个Bug。
关于编译器Bug的说明
- Clang的推导指南编译失败:该推导指南符合C++标准语法,Clang无法编译属于需要上报的兼容性Bug。
- MSVC的原始构造函数异常行为:原始实现中MSVC在
fwd_wrapper中执行复制而非移动、多余调用复制构造函数的问题,属于MSVC模板参数推导和重载解析逻辑的Bug,新版编译器通过拆分构造函数的方式规避了该问题,旧版本的Bug需要上报。
内容的提问来源于stack exchange,提问作者Gene
相关产品推荐
相关产品推荐

