web-dev-qa-db-ja.com

substr呼び出しの結果に割り当てるのがコンパイラエラーではないのはなぜですか?

最も厄介なエラーを発見したばかりですが、コンパイラがフラグを立てなかった理由がわかりません。私が次のように書いた場合:

string s = "abcdefghijkl";
cout << s << endl;

s.substr(2,3) = "foo";
s.substr(8,1) = '.';
s.substr(9,1) = 4;
cout << s << endl;

コンパイラーはこれに関して何の問題もありません、そして、印刷されたものに基づいて、割り当てステートメントは効果がないように見えます。対照的に、

s.front() = 'x';

基になる文字列を変更することで(frontが文字への参照を返すため)期待する効果があり、

s.length() = 4;

また、lengthは整数を返すため、左辺値ではないものに割り当てることができないというコンパイラエラーを生成するという期待される効果もあります。 (まあ、とにかくsize_t。)

では、なぜコンパイラはsubstr呼び出しの結果への割り当てについて文句を言わないのでしょうか?参照ではなく文字列値を返すので、割り当て可能ではありませんよね?しかし、私はこれをg++(6.2.1)とclang++(3.9.0)で試したので、バグではないようで、機密性も低いようです。 C++バージョンへ(03、11、14を試しました)。

29
blahedo

substr()の結果は_std::string_一時オブジェクトです。これは部分文字列の自己完結型のコピーであり、元の文字列のビューではありません。

_std::string_オブジェクトであるため、代入演算子関数があり、コードはその関数を呼び出して一時オブジェクトを変更します。

これは少し驚くべきことです。一時オブジェクトを変更して結果を破棄すると、通常は論理エラーが示されるため、一般に、状況を改善しようとする方法は2つあります。

  1. constオブジェクトを返します。
  2. 代入演算子で左辺値参照修飾子を使用します。

オプション1は、コードのコンパイルエラーを引き起こしますが、いくつかの有効なユースケースも制限します(たとえば、戻り値からmove- ing -const文字列から移動することはできません)。

オプション2は、左側が左辺値でない限り、代入演算子が使用されないようにします。すべてが同意するわけではありませんが、これは私見の良い考えです。 議論についてはこのスレッドを参照してください

とにかく; C++ 11でref-qualifiersが追加されたとき 提案されました 戻ってすべてのコンテナーの仕様をC++ 03から変更しましたが、この提案は受け入れられませんでした(おそらく、既存のコードを壊した)。

_std::string_は1990年代に設計され、後から考えると今日では貧弱に見えるいくつかの設計上の選択を行いましたが、私たちはそれに固執しています。 _std::string_自体の問題を理解する必要があります。おそらく、ref-qualifiersやviewsなどを使用して、独自のクラスで問題を回避する必要があります。

45
M.M

コードがコンパイルされる理由は、それが合法的なC++であるためです。これが何が起こっているのかを説明するリンクです。

https://accu.org/index.php/journals/227

かなり長いので、最も関連性の高い部分を引用します。

クラス以外の右辺値は変更できず、cv修飾型を持つこともできません(cv修飾は無視されます)。それどころか、クラスの右辺値は変更可能であり、そのメンバー関数を介してオブジェクトを変更するために使用できます。また、cv修飾タイプを持つこともできます。

したがって、std::string::lengthから返される右辺値に割り当てることができない理由は、それがクラスのインスタンスではないためです。また、std::string::substrから返される右辺値に割り当てることができる理由は、クラスのインスタンス。

言語がこのように定義されている理由はよくわかりませんが、そのように定義されています。

12
danieltm64

コードを見てください:

s.substr(2,3) = "foo";

substrへの関数呼び出しは、オブジェクトと一時値である文字列を返します。その後、このオブジェクトを変更します(実際には、std::stringクラスからオーバーロードされた代入演算子を呼び出します)。この一時オブジェクトは保存されません。コンパイラは、この変更された一時的なものを単に破棄します。

このコードは意味がありません。なぜコンパイラが警告を出さないのかと疑問に思うかもしれません。答えは、コンパイラの方が優れているかもしれないということです。コンパイラは、神ではなく人によって書かれています。残念ながら、C++では、無意味なコードや未定義の動作を引き起こすコードをさまざまな方法で記述できます。これは、この言語の側面の1つです。他の多くの言語と比較して、プログラマーからのより良い知識とより高い注意が必要です。

私はMSVC2015でコードを確認しました:

std::string s1 = "abcdef";
s1.substr(1, 2) = "5678";

正常にコンパイルされます。

7
Kirill Kobelev