为什么字符串不会出现“需要左操作数作为左值”的错误?

4
在下面的代码片段中,为什么第二行的2 + 3 = 5语句会报错,但是接下来的字符串拼接赋值语句却能够成功编译?
#include <string>

int main() {                                                                                                                                                  
   2 + 3 = 5;   //  Error: lvalue required as left operand of assignment                                                                                                                                             
   std::string("2") + std::string("3") = std::string("5");  // Compiles successfully. why?                                                                                                 
   return 0;                                                                                                                                                 
}

我的理解是表达式std::string("2") + std::string("3") = std::string("5")的左侧将产生一个临时变量,即rvalue。这意味着我正在向rvalue赋值——就像2 + 3 = 5一样。因此,它也应该会给出需要lvalue作为赋值操作的左操作数的错误提示。但事实并非如此。

1
简化后的代码 std::string("3") = std::string("5"); 仍然可以编译通过。 - Richard Critten
1
可能是C ++函数返回rvalue,但可以分配新值?的重复问题。 - Davis Herring
1
刚刚检查了大多数标准容器,发现它们都没有删除operator=的rvalue版本,例如basic_string&basic_string :: operator =(CharT ch)&& = delete;对于任何容器都没有执行。因此,我想这是一个有意的决定。 - Richard Critten
2个回答

3

说明

对于类类型,赋值由复制和移动赋值运算符实现。 std::string 是一个类,因此

std::string("2") + std::string("3") = std::string("5")

仅仅是语法糖,意思是

(std::string("2") + std::string("3")).operator=(std::string("5"))

operator=是一个成员函数。通常,成员函数可以在左值和右值上调用。因此,这个表达式是有效的。

标准参考

对于非重载的operator=(例如对于int):[expr.ass]/1

赋值运算符(=)和复合赋值运算符都是从右向左分组的。所有这些操作都需要修改的左值作为其左操作数,并返回指向左操作数的左值。
[...]

对于重载的operator=(例如对于std::string):[expr.ass]/4

如果左操作数是类类型,则该类必须是完整的。对类对象的赋值由复制/移动赋值运算符定义([class.copy],[over.ass])。

(以上强调为所有内容)


1
更奇怪的是,赋值返回一个左值而不是右值。 - Davis Herring
2
@DavisHerring 是的。我认为std::string一开始就没有设计用于rvalue赋值,因此它不会影响值类别的传播。 - L. F.
1
@L.F. 就我所知,这是所有标准容器。 - Richard Critten
2
@UnSat: ...它的身份和存储方式是暂时实现为xvalue对象表达式用于(隐式)函数调用。但是说“它返回std::string&,因此它是一个lvalue。”要简单得多。 - Davis Herring
2
@UnSat 这只是对值类别的简化观点。它可能对一些简单的场景有帮助,但不要将其视为定义。 - Lightness Races in Orbit
显示剩余3条评论

2

这条规则仅适用于内置类型的对象,例如 int

它不适用于类。

这条规则可能对类来说过于严格了,因为类的运算符可以被重载以执行各种操作……或者在 C++ 诞生时被认为过于严格,但放松内置类型的规则会破坏旧的 C 代码,这是不允许的。

无论如何,你添加的结果实际上是一个左值,因为当你从函数返回一个左值引用,比如 std::string&,你得到的就是一个左值。这是尽管加法的两个操作数都是右值表达式和临时对象。这在这里可能有点令人惊讶,现在语言给了我们工具来做到更好地“传播值类别”,有提议在这种情况下做到更好的“值类别传播”


2
加法的结果(即+表达式,它是一个operator+调用)是一个右值。赋值的结果是一个左值。 - aschepler
1
@aschepler 嗯,你说得对。我从未想过内置操作的规则也适用于重载操作符。确实,最近有一个案例明显没有遵循这样的规则。我感觉有些不一致。 - Lightness Races in Orbit
它没有使用内置运算符的规则。由于使用的operator+函数返回一个std::string(而不是引用),因此将两个字符串相加的结果是rvalue。 - aschepler
@aschepler .... 哦,是啊。我为什么会想到复合赋值呢?我需要休假了。 - Lightness Races in Orbit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接