Подтвердить что ты не робот

Почему это не ошибка компилятора для назначения на результат вызова 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;

также имеет ожидаемый эффект от генерирования ошибки компилятора, жалуясь на то, что вы не можете назначить то, что не является lvalue, потому что length возвращает целое число. (Ну, a size_t в любом случае.)

Итак... почему компилятор не жалуется на назначение результата вызова substr? Он возвращает строковое значение, а не ссылку, поэтому его нельзя назначать, правильно? Но я пробовал это в g++ (6.2.1) и clang++ (3.9.0), поэтому он не кажется ошибкой, и он также, похоже, не чувствителен к версии С++ ( попробовал 03, 11, 14).

4b9b3361

Ответ 1

Результатом substr() является временный объект std::string - это автономная копия подстроки, а не представление исходной строки.

Будучи объектом std::string, у него есть оператор-оператор присваивания, и ваш код вызывает эту функцию для изменения временного объекта.

Это немного удивительно: изменение временного объекта и отбрасывание результата обычно указывает на логическую ошибку, так что в общем случае есть два способа улучшить ситуацию:

  • Возвращает объект const.
  • Использовать ref-определитель lvalue для оператора присваивания.

Вариант 1 приведет к ошибке компиляции для вашего кода, но также ограничивает некоторые допустимые варианты использования (например, move -из из возвращаемого значения - вы не можете перемещаться из строки const),

Вариант 2 не позволяет использовать оператор присваивания, если левая сторона не является значением l. Это хорошая идея ИМХО, хотя не все согласны; см. эту тему для обсуждения.

В любом случае; когда ref-qualifiers были добавлены в С++ 11 было предложено вернуться и изменить спецификацию всех контейнеров с С++ 03, но это предложение не было принято (предположительно, если оно сломало существующий код).

std::string был разработан в 1990-х годах и сделал некоторые варианты дизайна, которые сегодня кажутся плохими, но мы застряли с ним. Вам нужно просто понять проблему для std::string и, возможно, избегать ее в своих собственных классах, используя ref-qualifiers, или представления или что-то еще.

Ответ 2

Причина, по которой ваш код компилируется, объясняется тем, что он является законным С++. Здесь ссылка, которая объясняет, что происходит.

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

Это довольно долго, поэтому я приведу наиболее релевантную часть:

Неклассовые значения r не изменяются и не могут иметь cv-квалифицированные типы (cv-квалификации игнорируются). Напротив, класс rvalues ​​являются модифицируемыми и могут использоваться для модификации объекта через его функции-члены. Они также могут иметь типы с квалификацией cv.

Таким образом, причина, по которой вы не можете назначить значение rvalue, возвращаемое std::string::length, заключается в том, что это не экземпляр класса, и причина, которую вы можете назначить rvalue, возвращаемую из std::string::substr, состоит в том, что это экземпляр класса.

Я не совсем понимаю, почему язык определен таким образом, но как он выглядит.

Ответ 3

Посмотрите на код:

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

Вызов функции substr возвращает строку, то есть объект и временное значение. После этого вы изменяете этот объект (фактически, вызывая перегруженный оператор присваивания из класса std::string). Этот временный объект никоим образом не сохраняется. Компилятор просто уничтожает это измененное временное.

Этот код не имеет смысла. Вы можете спросить, почему компилятор не дает предупреждения? Ответ заключается в том, что компилятор может быть лучше. Составители написаны людьми, а не богами. К сожалению, С++ допускает множество различных способов написания бессмысленного кода или кода, который вызывает поведение undefined. Это один из аспектов этого языка. Это требует лучшего знания и повышенного внимания программиста по сравнению со многими другими языками.

Я только что проверил с MSVC 2015, код:

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

отлично компилируется.