将变量分配给对其自身的引用时,中间修改然后由函数调用返回时,结果应该是什么?
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)值替换左值x
(lvalue-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
可能是 5
或 6
。
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取决于编译器和编译器版本。
#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)值替换左值x
(lvalue-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
可能是 5
或 6
。
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取决于编译器和编译器版本。