3
#include <iostream>
#include <vector>

using namespace std;

int main(void){
    vector<int> v = {1, 2, 3};
    auto& it = v.begin();

    // 1.
    v.begin() += 1;
    cout << *(v.begin()); // output is 1

    // 2.
    cout << *(v.begin()+1); // output is 2
}

The upper code shows that v.begin() += 1; doesn't work as I intended. I don't know the actual difference between code1 and code2. Is v.begin() lvalue or rvalue? Or nothing?

When it is inserted instead of v.begin() both case returns value 2. Why such things happens?

kims
  • 85
  • 5
  • What do you think `v.begin()` returns each time? – David C. Rankin Dec 06 '21 at 01:00
  • v.begin() cannot be changed? then why `v.begin()+=1` code is not error? – kims Dec 06 '21 at 01:03
  • 1
    The code that "doesn't work" works as intended. You are getting an iterator, adding one to it, and then that iterator is destroyed, never to be used again. That such code is not useful does not make it a C++ error. You are allowed to write C++ code that isn't useful. The compiler won't stop you. – Drew Dormann Dec 06 '21 at 01:05

1 Answers1

5

Is v.begin() lvalue or rvalue?

It's an rvalue.

auto& it = v.begin();

This is ill-formed because an lvalue reference to non-const cannot be bound to an rvalue.


v.begin() += 1;

This advances the temporary iterator to the beginning, and discards the result.

It "works" if the goal is to do nothing useful.

cout << *(v.begin()); // output is 1

This indirects through the iterator to beginning.

cout << *(v.begin()+1); // output is 2

This advances the iterator to the beginning, and instead of discarding the result, it indirects through the resulting iterator.

v.begin() cannot be changed? then why v.begin()+=1 code is not error?

You can change a temporary object returned by the function. But changes to that temporary object have no effect on what v.begin() will return in future.


Yes, v.begin() += 1 is a compound assignment to an rvalue. If you come from C, you may be surprised by this since you may have learned that "only lvalues may be the left hand operand of an assignment". But in C++ that only applies to fundamental types, and it does not apply to class types in general.

When defining assignment operator overloads for your own classes, it may be useful to use lvalue ref qualified member functions which would prevent direct assignment to an rvalue, and thus would prevent code such as v.begin() += 1.

Ref qualifiers were added to the standard later (in C++11), so the pre-existing standard library classes couldn't have those qualifiers, and so assigning rvalues of standard types is unfortunately possible. There was a proposal to add the qualifiers, but it didn't pass due to concerns of backward compatibility. I don't know why, but specifications of new standard classes haven't had qualified assignment operators either.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • `for(auto& it = v.begin(); it != v.end(); it+=1){ cout << *it; }` I usually write code like this. and you mean `auto& it = v.begin()` this is a ill-formed code? – kims Dec 06 '21 at 01:14
  • 2
    @kims Yes, it is ill-formed. – eerorika Dec 06 '21 at 01:16
  • @kims I think MSVC allows it. But you really shouldn't use it. – bolov Dec 06 '21 at 01:17
  • @kims To expand, you should write `auto it = v.begin()`. – eerorika Dec 06 '21 at 01:24
  • 1
    @kims _"I usually write code like this"_ Why do you use `+=1` instead of `++`? And why not just use `for (const auto& elem : v) { std::cout << elem; }` instead of such error-prone manual loops? – heap underrun Dec 06 '21 at 01:33