Почему --string :: end () компилируется, а --string.size () нет? - PullRequest
2 голосов
/ 24 апреля 2019

код

std::string str{ "foo" };
auto lastCharIndex{ --str.size() };

создает ошибку компилятора lvalue required as decrement operand, но

auto lastCharIterator{ --str.end() };

нет. Почему это?

Ответы [ 3 ]

4 голосов
/ 24 апреля 2019

string::size возвращает скалярный тип (какое-то целое число) по значению, а встроенное выражение префикса - -- недопустимо для значений prvalues.Напротив, для перегруженных операторов (для пользовательских типов), которые по сути являются просто функциями, такое ограничение не применяется.Поэтому допустимость --str.end() зависит от того, является ли string::iterator определяемым пользователем типом (какой-то тип класса) или скалярным типом (например, указатель).Однако это не указано и может варьироваться в зависимости от настроек оптимизации.

1 голос
/ 24 апреля 2019

Я хотел бы добавить, что вы можете настраивать пользовательские функции-члены, такие как ++, которые ведут себя по-разному при вызове через ссылки lvalue и rvalue. Например, вы можете определить пользовательский класс iterator, который не позволяет вам временно вызывать модифицирующие функции-члены. Для этого вам необходимо использовать ссылочные квалификаторы .

class iterator {
public:
    iterator operator++() & { // for lvalues
        std::cout << "incrementing...\n";
    }
    iterator operator++() && = delete; // for rvalues
};

С этими квалификаторами вы все еще можете изменять lvalues:

iterator it = ...;
++it; // totally fine

, но теперь вы можете предотвратить изменение временных значений, что приведет к созданию пользовательского класса, более совместимого со встроенными типами, такими как size_t.

++(iterator{}); // ERROR

Живой пример здесь

Я не уверен, что стандарт говорит об этом для типа итератора std::string, но в принципе, вы можете сделать это для своих собственных итераторов и других классов, где бы вы ни думали, что модификация временного всегда является ошибкой .

1 голос
/ 24 апреля 2019

Операндом оператора декремента (и приращения) для арифметических типов фундаментальных типов должно быть lvalue.std::string::size возвращает значение (т. Е. Не ссылку) целочисленного типа, поэтому вызов функции str.size() является предварительным значением.Prvalues ​​не являются lvalue, поэтому --str.size() является некорректным.

Операнд перегрузки оператора декремента (и приращения) для типа класса может вызываться для rvalue, если только перегрузка не является lvalue ref-qualified.Если --str.end() правильно сформирован, то мы можем сделать вывод, что итератор является типом класса (указатель будет примером типа итератора, который не является типом класса), и перегрузка оператора декремента действительно не имеет lvalue ref-классификатор.Насколько я могу судить, не определены ли функции-члены итераторов стандартных контейнеров.

PS То же самое относится к (составным) операторам присваивания: l eft hand операндприсваивание также должно быть lvalue (если это не тип класса с перегрузкой, которая не является lvalue ref-qualified).Фактически, это то место, где происходит значение « l » в l .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...