web-dev-qa-db-ja.com

operator []をオーバーロードしていて、「割り当ての左オペランドとして必要な左辺値」エラーを取得しない

これは、「割り当ての左オペランドとして必要な左辺値」エラーの質問のすべてを逆にしたものです。
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が一時的で、特殊な演算子=がない場合、なぜこれが同じように文句を言わないのですか?別の質問ですが、これを不満にする方法はありますか?明らかにこの方法で使用した場合、これはコーディングエラーです。

16
ByteMe95

これを不満にする方法はありますか?

Ref-qualifierで明示的にデフォルト設定された代入演算子を使用できます。

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

これにより、右辺値の割り当てが正しくありませんが、左辺値の割り当ては整形式のままです。

割り当て演算子を宣言すると、暗黙の移動割り当てが無効になるため、必要に応じて(デフォルトとして、適切な場合は、rvalue ref修飾子を使用して)定義も必要になる場合があることに注意してください。

Barが一時的であり、特殊な演算子=がない場合、なぜこれが同じように文句を言わないのですか?

暗黙的に生成された代入演算子は参照修飾されていないためです。

明らかにこの方法で使用した場合、これはコーディングエラーです。

右辺値の割り当ては、一般的にエラーではありません。参照のように動作すると想定されている一部の型では、右辺値の割り当ては自然です。これは、割り当てが一時オブジェクト自体ではなく、参照オブジェクトを変更するためです。

典型的な使用例は、右辺値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);

はい、暗黙の演算子が修飾されていて、非修飾には明示的な宣言が必要な場合、参照型が標準よりも例外的であることを考えると、より良いかもしれません。しかし、それは言語のあり方ではなく、言語の変更は後方互換性のない変更です。

17
eerorika

はい、これらのメソッドを削除してこれをコンパイルエラーにする方法があります。

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;
};
1
Quimby