Take a look at this code:
At first glance, it should output $$$3$$$ because that's what $$$f()$$$ returns, which is then assigned to $$$k_0$$$. But when executed, this code outputs $$$1$$$!
This code is very short and everything seems clear, we're simply assinging $$$k_0$$$ to the return value of $$$f()$$$ which is $$$3$$$, and the push_back in $$$f()$$$ clearly doesn't affect the result... or does it?
How an assingment operation really works
It may seem obvious at first, we just copying one value to another, but let's see how compiler actually handles this operation.
Consider this code. (Source)
This code can output any permutation from '$$$abcabc$$$' to '$$$cbacba$$$', because the evaluation order of function arguments and operands in addition is unsequenced in C++. This flexibility allows compilers to optimize performance by evaluationg arguments in any order (or even simultaneously).
And assignment is just another operation, meaning it's left and right operands can be evaluated in any order.
In our original example here's what happens:
1) Compiler evaluates the left operand (obtaining a reference to $$$k_0$$$).
2) Then, it calls $$$f()$$$.
3) Finally, it assigns the return value ($$$3$$$) to the previously obtained reference.
And if you're familiar with how vectors work, you might already see the issue.
How vectors work
A vector is a dynamic container, it's size can change, and elements can be added ('push_back') or removed ('pop_back').
Every vector has a capacity (the allocated memory space, initially empty). And when you call push_back, one of two things happens:
1) Capacity is sufficient. The new element is placed at the end, and the vector's 'end()' pointer is incremented.
2) Capacity is insufficient. A new memory block (typically $$$2x$$$ the current size) is allocated, existing elements are copied/moved to the new location, and then everything is the same as in the first case.
Vectors store elements contiguosly for fast indexing, so if reallocation occurs, the entire vector moves to a different memory location.
So what happens?
1) k.push_back(1) initializes the vector with $$$1$$$.
2) In $$$k_0 = f()$$$, the compiler:
2.1) First evaluates the left side (obtaining a reference to $$$k_0$$$).
2.2) Then calls $$$f()$$$, which performs k.push_back(3), and if reallocation occurs, reference to $$$k_0$$$ changes.
2.3) Finally, it assigns $$$3$$$ to the now invalid reference, leaving the actual $$$k_0$$$ unchanged ($$$1$$$).
Conclusion
You can avoid this bug in many different ways, such as:
1) Storing the value of $$$f()$$$ in a variable and then assigning it to $$$k_0$$$
2) Preallocate memory (do k.reserve(2) in this example) to ensure no reallocation happens during push_back.
3) Push_back to the vector before or after the function if possible.
I faced this mistake while debugging a Cartesian tree problem, and spent 2-3 hours figuring it out. Hopefully, this blog helps you avoid the same mistake or at least know how to fix it.
Have you faced a similar mistake?









Compiling the first code in codeforces custom test, results in 3 as the output
I also ran it with my own system, still 3
Left and right operands of assignment can be evaluated in any order, so this is possible. However, on my system it gives 1.
The evaluation order is defined in C++17 and later, so you should get 3 in those versions. Before C++17, this was undefined behavior. See Order of evaluation in cppreference (look at rule 19).
o_0
Another solution is to use Rust (similar code is not compiled)
A similar issue can also occur when using
push_backin a range-based for loop:This will output
0 1 5 <random number>instead of0 1 5 6I've had this bug during a live contest; SWERC 2021-2022, problem C. You can see the frustration from the scoreboard.
I honestly have no clue how I was able to debug this in a 200+ line code, in the last hour (I didn't do the intended simpler solution). But it taught me a lesson for sure.
I hope it didn't ruin your performence in a live contest!
another faulty one with the same logic:
Used a pointer tree* to a vector,
forgot to do reserve, passes public/open test case, always fail at closed test case.
Wasted around 30 mins debugging.
"And assignment is just another operation, meaning it's left and right operands can be evaluated in any order." I would like to point out that this is no longer true since C++17.
§[expr.ass]¶1 from the latest C++17 draft:
It can also be found in rule 19 in the link mentioned in the blog.
This means things like
k[0] = f();(from the blog) ormap[x] = map.size();(something I use often) can be used safely if the online judge uses C++17 or later. If it is the case then I suggest updating the local compilation setup to match the judge to prevent issues like this.