我有一个问题出现在 GCC 和 Clang 中,但没有出现在 MSVC 中。有问题的部分归结为:
#include <utility>
#include <string>
#include <iostream>
auto mkStrings() -> std::pair<std::string, std::string>
{
std::string r = "r";
return { r, "s" + std::move(r) }; // !
}
int main()
{
auto [r, s] = mkStrings();
std::cout << "'" << r << " " << s << "'" << std::endl;
}
在 MSVC 上,我得到:
'r sr'
在 GCC 12.2.0 和 Clang 15.0.7 上,它输出:
' sr'
(在 Clang 16.0.1 上,编译器出现段错误。)
我很确定
std::move
是个问题,但我不明白为什么。如果我误认为初始化列表是从左到右评估的,我什至会查找,但根据这个问题的答案他们是。
当然,我只是删除了
std::move
,但我想了解为什么我必须这样做。
因为花括号初始化器中的求值顺序是未指定的。 GCC 和 Clang 似乎首先评估
"s" + std::move(r)
,然后 r
是空的。
21) 括号初始化器中以逗号分隔的表达式列表中的每个表达式都被评估为函数调用(不确定顺序)
以下构造函数用于初始化:
template <class U1, class U2>
constexpr pair(U1&& x, U2&& y);
(在 C++23 中,将添加默认模板参数,但这不影响答案。)
因为您正在使用花括号初始化,所以参数
x
将在参数y
之前被初始化。然而,x
和 y
都必须在 ctor-initializer 被评估之前被初始化(它实际上初始化了 first
和 second
成员)。
所以
x
首先绑定到 r
,然后 y
绑定到表达式 "s" + std::move(r)
的临时结果。在这一点上,r
可能会被移走。
之后,构造函数使用
std::forward<U1>(x)
来初始化first
。但是此时,x
所指的对象的移动已经执行了。