为何花括号初始化列表中使用后显式std::move会破坏返回值?
为什么std::move会导致GCC/Clang下的异常输出(MSVC正常)
你的问题核心在于C++标准中关于列表初始化表达式求值顺序的历史规则差异,以及std::move触发的未定义行为:
求值顺序的标准差异
你查到的"花括号初始化列表按左到右求值"是C17及以后的明确规则。在C17之前,std::pair的列表初始化(即return {r, "s" + std::move(r)};)中,两个元素的表达式求值顺序是未指定的——编译器可以自由选择先计算右边的"s" + std::move(r),再计算左边的r。- MSVC默认可能启用了C++17的求值顺序规则,或者其实现强制左到右求值,所以先复制左边的
r到pair的第一个成员,再移动右边的r,结果符合预期。 - GCC/Clang在非C++17模式下,会优先执行
std::move(r):移动操作会把r的内部字符资源转移走,让原r变成空字符串,后续左边的r只能取到空值,导致输出异常。Clang16的段错误是因为移动后的std::string处于"合法但不可用"的状态,后续访问触发了更严重的未定义行为。
- MSVC默认可能启用了C++17的求值顺序规则,或者其实现强制左到右求值,所以先复制左边的
为什么移除
std::move就正常
去掉std::move后,右边的表达式是"s" + r,这里会复制r的值来拼接字符串,无论编译器先算左边还是右边:- 如果先算左边:复制
r到pair的第一个成员,右边再复制r拼接,结果正常。 - 如果先算右边:复制
r拼接成新字符串,左边再复制r,结果依然正常。
本质是复制操作不会修改原r的状态,所以无论求值顺序如何,r的有效值都能被两边用到。
- 如果先算左边:复制
验证C++17规则的效果
给GCC/Clang加上-std=c++17编译选项后,输出会和MSVC一致:'r sr'。因为C++17明确规定了列表初始化的元素表达式必须按左到右顺序求值,先处理左边的r,再处理右边的std::move(r),此时左边已经完成复制,右边移动r不会影响第一个成员的值。
内容的提问来源于stack exchange,提问作者Bolpat
相关产品推荐
相关产品推荐

