7

I can't seem to get around this specific problem for some time now. For example if I have the following code:

void foo(std::vector<int>::iterator &it) {
    // ...
}

int main(){
    std::vector<int> v{1,2,3};
    foo(v.begin());
}

I would get compile error:

initial value of reference to non-const must be an lvalue.

And my guess would be that I get the error because a.begin() returns a rvalue.

If so how is it possible that the following expression works:

v.begin()=v.begin()++;

if v.begin() is a rvalue?

alter_igel
  • 6,899
  • 3
  • 21
  • 40
  • It is legal to call non-const member functions on temporaries of class type. As in `v.begin().operator=(v.begin().operator++())`. It is sometimes useful, but it does lead to odd code compiling when those member functions are overloaded operators. – Igor Tandetnik Dec 27 '20 at 00:19
  • If they did not specify that iterators should prohibit assignment on rvalues in standard, this looks like a defect to me – Slava Dec 27 '20 at 00:26
  • 2
    This is related https://stackoverflow.com/questions/5890382/operator-overload-which-permits-capturing-with-rvalue-but-not-assigning-to – Slava Dec 27 '20 at 00:27
  • `foo` should take the iterator by value. That’s how iterators are intended to be used. It’s very unusual to traffic in references to iterators. – Pete Becker Dec 27 '20 at 00:40
  • @alterigel OP's concern why assignment to rvalue compiles. It does compile on gcc in C++14 mode at least, so it is not related to pesky MS extention – Slava Dec 27 '20 at 00:43
  • @PeteBecker question is not about `foo()` it is there just to show that `begin()` returns rvalue. – Slava Dec 27 '20 at 00:44

1 Answers1

9

The reason is historical. In the initial days of the language, there was simply no way for user code to express that a type's copy-assignment operator should only work on l-values. This was only true for user-defined types of course; for in-built types assignment to an r-value has always been prohibited.

int{} = 42; // error

Consequently, for all types in the standard library, copy-assignment just "works" on r-values. I don't believe this ever does anything useful, so it's almost certainly a bug if you write this, but it does compile.

std::string{} = "hello"s; // ok, oops

The same is true for the iterator type returned from v.begin().

From C++11, the ability to express this was added in the language. So now one can write a more sensible type like this:

struct S
{
  S& operator=(S const &) && = delete;
  // ... etc
};

and now assignment to r-values is prohibited.

S{} = S{}; // error, as it should be

One could argue that all standard library types should be updated to do the sensible thing. This might require a fair amount of rewording, as well as break existing code, so this might not be changed.

cigien
  • 57,834
  • 11
  • 73
  • 112