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