Why can I use assignment operator on begin() even if it is an rvalue?
Asked Answered
W

1

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?

Wysocki answered 27/12, 2020 at 0:10 Comment(6)
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.Earthman
If they did not specify that iterators should prohibit assignment on rvalues in standard, this looks like a defect to mePhenetidine
This is related #5890882Phenetidine
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.Sicyon
@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 extentionPhenetidine
@PeteBecker question is not about foo() it is there just to show that begin() returns rvalue.Phenetidine
C
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.

Cruet answered 27/12, 2020 at 0:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.