Search code examples
c++c++20assignment-operator

Why my assigning an object itself doesn't work properly?


AFAIK, for an operator that doesn't guarantee the order of evaluation of its operand(s), we should not modify the operand more than once.

Here I have this snippet that I wrote:

  • I'm compiling it using gcc with the C++20 standard: -std=c++2b.
int main(){
    std::string s = "hello";
    auto beg = s.begin();
    *beg = *beg++;
    std::cout << s << "  " << *beg << std::endl;
}

Why do I get this output?

hhllo  h

This:

*beg = (*beg)++;
std::cout << s << " " << *beg << '\n';

yields this output:

hello  h
  • Why is the value 'h' not incremented to 'i'?

  • What happens here? Normally, this assigns the first character in s back to itself then increments the beg iterator. But the iterator is not incremented, but the value changed! I bet that it is because of Undefined Behavior, but if so then can someone explain this to me?

  • Did the C++17 standard add the assignment operator to the 'sequenced' operators? If so, why doesn't it work properly in my example?


Solution

  • All of these results are correct. Your misunderstanding comes from not knowing how postfix increment works.

    *beg = *beg++;
    

    By the rules of C++ operator precedence, postfix operators happen first. So this is *beg = *(beg++).

    C++17 forced assignment operators to completely evaluate all expressions on the right-hand side, including all side-effects, before evaluating the left-hand side.

    Postfix iterator increment increments the lvalue, but it returns a copy of the original value. The original value is an iterator pointing at the first character. So after evaluating beg++, beg will have changed its position (therefore pointing at the second character). But the value of the expression is an iterator pointing at the first.

    Then this iterator is derefenced, thus returning a reference to the first character.

    After that, the lhs is evaluated. beg as previously noted, points to the second character. So *beg is a reference to the second character, which gets assigned to the value of the first character.


    *beg = (*beg)++;
    

    So this is the other way around. *beg is a reference to the first character, which is then postfix incremented. This means that the object referenced by *beg is incremented, but the value of the expression is a copy of the original value. Namely, the character as it was before being incremented: 'h'.

    You then assign this character to the first character, therefore overwriting what you just incremented.

    BTW: if you have to go into this detail to figure out what an expression is doing, you shouldn't write your expression that way. Avoid postfix expressions unless they are absolutely essential in some way.