Search code examples
c++operator-overloadingc++17language-lawyerstdatomic

std::atomic address after store


I can't seem to get the address of an atomic object after a store.

e.g.

std::atomic<int> i;
std::atomic<int>* p = &++i; // doesn't work
auto* p = &++i; // doesn't work
// below works:
++i;
auto* p = &i;

What's happening here and why?

To clarify: I know it returns an r-value. Why doesn't it return the original object, this? Is this a purposeful design choice or was it an oversight?

More specifically, what's happening under-the-hood for this requirement?


Solution

  • While the pre-increment operator usually returns its operand by reference, in the case of std::atomic integers, it returns the new value as a temporary. So in your example ++i does not return a reference to the atomic<int> i itself, it returns the new value of i (i.e. an int). You can see this at: https://en.cppreference.com/w/cpp/atomic/atomic/operator_arith

    It would be misleading and even dangerous to return a reference to the original atomic<int>, because to access the int value through this reference would require a second, separate read operation — so its value might be different from the value at the time of increment. (This isn't particularly relevant your example code, since you are only trying to obtain a pointer to the referenced object, but some code will actually access the value after ++ so this is why returning a reference isn't possible.)

    In other words, if ++i returned a reference to the atomic<int> i, then

    int j = ++i;
    

    would be equivalent to

    ++i;
    // ...other threads may modify the value of `i` here!...
    int j = i;
    

    The whole point of atomics is to perform reads and writes together as an indivisible operation, so ++i must internally use hardware/OS atomic operations to simultaneously read and increment the integer, so the new value is returned as a temporary.

    If you're curious to see what's under the hood, here is libc++'s implementation where you can see that operator++ simply calls into fetch_add(1) and returns the result + 1.