std::promise存储const类型在MSVC/GCC/Clang中的实现差异
问题现象
近期使用MSVC编译此前可在GCC、Clang下正常构建的代码时,触发了如下截断的编译错误:
C:/data/msvc/14.33.31424-Pre/include\future(311) error C2678: binary '=': no operator found which takes a left-hand operand of type 'const Result' (or there is no acceptable conversion) <source>(7): note: could be 'Result &Result::operator =(Result &&)' <source>(7): note: or 'Result &Result::operator =(const Result &)'
该问题的最小复现代码如下:
#include <future> #include <iostream> struct Result { int data{0}; }; // 移除const限定即可解决MSVC下的编译问题 using result_t = const Result; using promise_result_t = std::promise<result_t>; auto compute_result(promise_result_t result) { Result some_result{10}; result.set_value(std::move(some_result)); } int main() { promise_result_t my_promise; auto my_future = my_promise.get_future(); std::thread thread(compute_result, std::move(my_promise)); std::cout << "result: " << my_future.get().data; thread.join(); return 0; }
不同编译器下的编译表现:
- Clang 11 环境下编译成功
- GCC 12 环境下编译成功
- MSVC 19 环境下编译触发报错
原因解析
首先明确核心结论:用const限定类型作为std::promise的模板参数本身不符合C++标准要求,MSVC的报错是严格遵循标准实现的结果,GCC、Clang能编译通过只是实现细节带来的巧合。
- 标准层面的约束
C++标准对std::promise<T>的模板参数有明确要求:T必须是可赋值的具体类型,不能是const限定类型、引用类型、函数类型或抽象类。因为promise内部维护的共享状态需要支持set_value时的值写入操作,const类型天然不支持赋值,本身就不满足promise对类型的要求。 - 不同编译器的实现差异
- MSVC的标准库实现:在共享状态中直接存储
T类型的实例对象。调用set_value传入右值时,内部逻辑会对已经存在的T实例调用移动赋值运算符写入新值。当T是const Result时,赋值运算符的左操作数是const类型,无法匹配到可用的赋值重载,就触发了你看到的C2678错误。 - GCC(使用libstdc++)、Clang(使用libc++)的标准库实现:共享状态不直接存储
T对象,而是先分配一块满足对齐要求的原始内存,直到调用set_value时才通过placement new在这块原始内存上直接构造T对象,全程不会调用赋值运算符,只会调用构造函数。构造const类型对象本身是合法的,因此绕开了const类型无法赋值的限制,最终表现为可以正常编译运行。
不要依赖GCC、Clang的这个宽松行为,这种写法本身属于标准未定义行为,随时可能在标准库版本更新后失效。移除std::promise存储类型的const限定,才是符合标准规范的正确解决方案。
内容的提问来源于stack exchange,提问作者Eugene Kuznetsov
相关产品推荐
相关产品推荐

