将变量分配给对其自身的引用时,中间修改然后由函数调用返回时,结果应该是什么?

What should the result be when assigning a variable to a reference to itself, in-between modified and then returned by a function call?

#include <iostream>

int& addOne(int& x)
{
    x += 1;
    return x;
}

int main()
{
    int x {5};
    addOne(x) = x;
    std::cout << x << ' ' << addOne(x);
}

我目前正在学习左值和右值并进行了一些试验,结果似乎得到了相互矛盾的结果。 https://godbolt.org/z/KqsGz3Toe produces an out put of "5 6", as does Clion and Visual Studio, however https://www.onlinegdb.com/49mUC7x8U 产生结果“6 7”

我认为因为 addOne 正在调用 x 作为引用,它会明确地将 x 的值更改为 6,尽管它被称为左值。正确的结果应该是什么?

自 C++17 起,计算顺序被指定为 = 的操作数被计算为 right-to-left,<< 的操作数被计算为 left-to-right,匹配这些运算符的结合性。 (但这并不适用于所有运算符,例如 + 和其他算术运算符。)

所以在

addOne(x) = x;

首先计算 right-hand 边的值,得到 5。然后函数 addOne 被调用,它对 x 做什么并不重要,因为它 return 是对它的引用,right-hand 值 5 已分配。

形式上,首先评估right-hand端意味着我们用它持有的(pr)值替换左值xlvalue-to-rvalue转换).然后我们调用addOne(x)修改左值x所指的对象

因此,假设临时变量保存各个评估的结果,该行等同于(新变量引入的额外副本除外,这在 int 的情况下无关紧要) :

int t = x;
int& y = addOne(x);
y = t; // same as x = t, because y will refer to x

然后在行

std::cout << x << ' ' << addOne(x);

我们先计算并输出x,得到5,然后调用addOne,得到6.

所以该行等同于(简化,知道 operator<< 将再次 return std::cout):

int t1 = x;
std::cout << t1 << ' ';
int t2 = addOne(x);
std::cout << t2;

输出 5 6 是自 C++17 以来唯一正确的。


在C++17之前,赋值运算符两侧的求值顺序是unsequenced.

在同一个标​​量上(在您赋值的 right-hand 侧)进行未排序的标量修改和值计算通常会导致未定义的行为。

但是由于您将 x 的增量放入函数中,因此有一条附加规则表明函数体的执行只是 不确定顺序 与其他评估调用上下文保存了这个。这意味着该行将不再有未定义的行为,但分配两侧的评估发生的顺序可以是 left-first 或 right-first.

这意味着我们不知道是先计算 x 再计算 addOne(x) 还是相反。

因此在该行之后,x 可能是 56

6 如果评估等同于

将获得
int& y = addOne(x);
int t = x;
y = t;

然后在行

std::cout << x << ' ' << addOne(x);

pre-C++17 同样的问题也适用。 << 的参数的计算顺序不确定,而不是 left-to-right,因此 addOne(x) 可以在 left-hand x 之前计算,即除了之前的订单,评估也可以等同于

int t2 = addOne(x);
int t1 = x;
std::cout << t1 << ' ' << t2;

在这种情况下 x 首先递增,然后它的新值被打印两次。

因此可能的程序输出可能是以下之一:

5 6
6 6
6 7
7 7

(从技术上讲,int t2 = addOne(x) 是两个评估:一个调用 addOne return 引用,然后 lvalue-to-rvalue 转换。这些可能与另一个交错发生评估,但这不会给出任何新的程序输出。)


如果您使用 GCC 或 Clang,您可以指定使用带有 -std=c++17 标志的 C++17(或更新版本,如 C++20),如果您使用 /std:c++17正在使用 MSVC。选择哪个标准版本by-default取决于编译器和编译器版本。