重载操作符[]时,不会出现“需要左值作为赋值的左操作数”错误。

15

这有点相反于所有“lvalue required as left operand of assignment”错误的问题。
我有一个重载了operator[]的类,但只重载了返回临时对象的版本。如果它返回一个int:

struct Foo
{
    int operator[]( int idx ) const { return int( 0 ); }
};

Foo f;
f[1] = 5;

如果返回一个左值,我将会得到正确的编译器错误。但是如果返回一个结构类型,编译器(在本例中为GCC 7.2)则不会发出任何警告:

struct Bar {};
struct Foo
{
    Bar operator[]( int idx ) const { return Bar(); }
};

Foo f;
f[1] = Bar();
为什么如果Bar是一个临时变量并且它没有专门的运算符=,它就不会以同样的方式抱怨? 另一个问题是,有没有办法让这个程序报错?显然,如果以这种方式使用它,这是一个编码错误。

2
测试用例简化为:Bar() = Bar();,这对我来说是可以编译的!我希望至少有一个警告。实时查看:https://godbolt.org/z/_IPcrd - Richard Critten
不确定是否可行,但如果将Bar的赋值运算符设为私有,则无法编译。另请参见防止返回值引用的复制构造和赋值 - Håkon Hægland
也许结构体有一个默认的赋值运算符,允许你在执行object[index] = Object()时进行赋值?这对我来说似乎相当明显...所以我进行了测试...结果发现如果删除赋值运算符,它会报错...对我来说似乎已经得到证实了... - Brandon
是的,将赋值运算符设为私有可以防止这种情况发生,但也会阻止我在需要进行赋值时进行赋值。 - ByteMe95
2个回答

17

有没有办法让这个发出警告?

您可以使用带有ref-限定符的显式默认赋值运算符:

struct Bar {
    Bar& operator=(const Bar&) & = default;
//                             ^

对于一个 rvalue 的赋值是非法的,而 lvalue 的赋值则是合法的。

需要注意的是,声明赋值运算符将禁用隐式移动赋值,因此如果需要的话,您可能还需要定义它(也可以默认为 defaulted,并可能具有 rvalue 引用限定符,如果适当的话)。

如果 Bar 是一个临时对象并且没有专门的 operator =,为什么它不会以相同的方式抱怨?

因为隐式生成的赋值运算符没有引用限定符。

显然,如果以这种方式使用,这是一个编码错误

rvalue 的赋值并非普遍都是错误的。对于一些被认为像引用一样行为的类型来说,将 rvalue 赋值是自然的。这是因为赋值修改了所引用的对象,而不是临时对象本身。

一个典型的用例是对 rvalue std::tie 进行赋值(来自cppreference的示例):

std::set<S> set_of_s; // S is LessThanComparable

S value{42, "Test", 3.14};
std::set<S>::iterator iter;
bool inserted;

// unpacks the return value of insert into iter and inserted
std::tie(iter, inserted) = set_of_s.insert(value);

是的,如果隐式操作符被限定并且需要显式声明非限定操作符,考虑到引用类型是特殊情况而不是规范,可能会更好。但这不是语言的现状,改变它会导致向后不兼容的变化。


1
我想补充一点,如果Bar有成员,其中移动操作与复制操作不同,那么您将需要遵守五个规则,因为这个声明必然会违反零规则。 - aschepler
@aschepler 添加了提及。有趣的是,这是五法则的明显例外,因为您根本不需要触摸析构函数。 - eerorika
复制赋值运算符还意味着没有隐式声明的移动构造函数(并且不建议使用隐式声明的复制构造函数)。 - aschepler

1

是的,通过删除这些方法可以将其变成编译错误:

Bar& operator=(const Bar&)&& =delete;
Bar& operator=(Bar&&)&& =delete;

请注意,这将禁用其他运算符和构造函数的自动生成,因此您必须定义它们所有:

struct Bar {
    Bar()=default;
    Bar(const Bar&) = default;
    Bar& operator=(const Bar&)&& =delete;
    Bar& operator=(Bar&&)&& =delete;
    Bar& operator=(const Bar&)& =default;
    Bar& operator=(Bar&&)& =default;
};

如果你有default,那么其他的是否隐式删除了,因此你不需要delete这些行? - Mooing Duck
1
@MooingDuck 是的,但它也会删除移动赋值运算符和移动构造函数。此外,这清楚地表达了意图。只有设置& =default;才能起作用,直到有人决定不需要&或应该添加&&为止。我认为这些限定符并不是很常见,因为它们很少见。是的,你可以并且应该添加注释,但那么你也可以使用=delete的代码来表达它。 - Quimby

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