使用std::async传递带显式拷贝构造函数的参数:GCC 11的编译行为是否更合规?
GCC 11的编译行为符合C++标准规范,并非回归问题
先直接给结论:GCC 11拒绝编译这段代码的行为是符合C++标准要求的,之前GCC 10及MSVC允许编译属于实现层面的宽松处理,而非标准规定的正确行为。
核心原因分析
问题的关键在于std::async打包参数的逻辑,以及C++对显式构造函数的转换规则:
std::async会把你传递的函数和参数打包进一个std::tuple,后续在异步执行的上下文里解包并调用目标函数。- 你的
struct A的拷贝构造函数被声明为explicit,根据C++标准,显式构造函数不能用于隐式转换场景——而std::async内部打包参数时,需要将临时对象A{}转换为tuple中的A类型成员,这个转换属于隐式转换范畴,因此标准不允许调用显式的拷贝构造函数。
错误信息背后的细节
GCC 11的错误提示could not convert '{...}' from '<brace-enclosed initializer list>' to 'std::tuple<void (*)(const A&), A>',本质是编译器尝试用列表初始化构造tuple,但其中初始化A类型成员时,需要调用显式的拷贝构造函数,这违反了标准中隐式转换不能使用显式构造函数的规则。
GCC 10及之前的libstdc实现可能在打包参数时,通过某种方式绕过了这个限制(比如直接使用显式构造的语法),但GCC 11之后的版本修正了这个实现,严格遵循了C标准的要求。
修复代码的几种方式
如果要让代码在GCC 11+正常编译,可以选择以下方案:
- 移除
A拷贝构造函数的explicit修饰(如果业务逻辑允许); - 显式调用拷贝构造函数来生成参数,比如:
auto f = std::async(std::launch::async, foo, A(A{})); - 或者使用
std::move将临时对象转为右值,显式触发构造调用:auto f = std::async(std::launch::async, foo, std::move(A{}));
内容的提问来源于stack exchange,提问作者Fedor
相关产品推荐
相关产品推荐

