Title
atomic compound assignment operators can cause undefined behavior when corresponding fetch_meow members don't
Status
ready
Section
[atomics.types.int][atomics.types.pointer][atomics.types.memop]
Submitter
Tim Song

Created on 2017-12-15.00:00:00 last changed 5 days ago

Messages

Date: 2026-07-31.16:46:35

Proposed resolution:

This wording is relative to N5054.

  1. Modify [atomics.ref.int] as indicated:

    constexpr value_type operator op=(value_type operand) const noexcept;
    

    -14- Constraints: is_const_v<integral-type> is `false`.

    -15- Effects: Equivalent to: return fetch_key(operand) op operand;

    -16- Returns: The value written by the call to fetch_key.

  2. Modify [atomics.ref.float] as indicated:

    constexpr value_type operator op=(value_type operand) const noexcept;
    

    -17- Constraints: is_const_v<floating-point-type> is `false`.

    -18- Effects: Equivalent to: return fetch_key(operand) op operand;

    -19- Remarks: If the result is not a representable value for its type ([expr.pre]) the result is unspecified, but the operations otherwise have no undefined behavior. The floating-point environment ([cfenv]) for atomic arithmetic operations on `floating-point-type` may be different than the calling thread's floating-point environment.

  3. Modify [atomics.ref.pointer] as indicated:

    constexpr value_type operator op=(difference_type operand) const noexcept;
    

    -14- Constraints: is_const_v<pointer-type> is `false`.

    -15- Effects: Equivalent to: return fetch_key(operand) op operand;

    -16- Returns: The value written by the call to fetch_key.

  4. Modify [atomics.ref.memop] as indicated:

    constexpr value_type operator++() const noexcept;
    

    -6- Constraints: is_const_v<referred-type> is `false`.

    -7- Effects: Equivalent to: return fetch_add(1) + 1operator+=(1);

    constexpr value_type operator--() const noexcept;
    

    -8- Constraints: is_const_v<referred-type> is `false`.

    -9- Effects: Equivalent to: return fetch_sub(1) + 1operator-=(1);

  5. Modify [atomics.types.int] as indicated:

    integral-type operator op=(integral-type operand) volatile noexcept;
    constexpr integral-type operator op=(integral-type operand) noexcept;
    

    -14- Constraints: For the `volatile` overload of this function, `is_always_lock_free` is `true`.

    -15- Effects: Equivalent to: return fetch_key(operand) op operand;

    -16- Returns: The value written by the call to fetch_key.

  6. Modify [atomics.types.float] as indicated:

    floating-point-type operator op=(floating-point-type operand) volatile noexcept;
    constexpr floating-point-type operator op=(floating-point-type operand) noexcept;
    

    -17- Constraints: For the `volatile` overload of this function, `is_always_lock_free` is `true`.

    -18- Effects: Equivalent to: return fetch_key(operand) op operand;

    -19- Remarks: If the result is not a representable value for its type ([expr.pre]) the result is unspecified, but the operations otherwise have no undefined behavior. Atomic arithmetic operations on `floating-point-type` should conform to the std::numeric_limits<floating-point-type> traits associated with the floating-point type ([limits.syn]). The floating-point environment ([cfenv]) for atomic arithmetic operations on `floating-point-type` may be different than the calling thread's floating-point environment.

  7. Modify [atomics.types.pointer] as indicated:

    T* operator op=(ptrdiff_t operand) volatile noexcept;
    constexpr T* operator op=(ptrdiff_t operand) noexcept;
    

    -15- Constraints: For the `volatile` overload of this function, `is_always_lock_free` is `true`.

    -16- Effects: Equivalent to: return fetch_key(operand) op operand;

    -17- Returns: The value written by the call to fetch_key.

  8. Modify [atomics.types.memop] as indicated:

    value_type operator++() volatile noexcept;
    constexpr value_type operator++() noexcept;
    

    -5- Constraints: For the `volatile` overload of this function, `is_always_lock_free` is `true`.

    -6- Effects: Equivalent to: return fetch_add(1) + 1operator+=(1);

    value_type operator--() volatile noexcept;
    constexpr value_type operator--() noexcept;
    

    -7- Constraints: For the `volatile` overload of this function, `is_always_lock_free` is `true`.

    -8- Effects: Equivalent to: return fetch_sub(1) + 1operator-=(1);

Date: 2026-07-31.00:00:00

[ 2026-07-31 LWG telecon; Status changed: New → Ready. ]

Date: 2026-07-15.00:00:00

[ 2026-07-30; Tim provides new wording ]

Date: 2019-04-15.00:00:00

[ 2019-04-15; JF Bastien comments and provides wording ]

As discussed by LWG during the San Diego 2018 meeting, Jens removed LWG 3047 from "P1236R1: Alternative Wording for P 0907R4 Signed Integers are Two's Complement".

This wording is relative to N4810.

  1. Modify [atomics.ref.int] as indicated:

    integral operator op=(integral operand) const noexcept;
    

    -7- Effects: Equivalent to: return static_cast<integral>(static_cast<make_unsigned_t<integral>>(fetch_key(operand)) op static_cast<make_unsigned_t<integral>>(operand));

  2. Modify [atomics.ref.memop] as indicated:

    T* operator++() const noexcept;
    

    -3- Effects: Equivalent to: return static_cast<T>(static_cast<make_unsigned_t<T>>(fetch_add(1)) + static_cast<make_unsigned_t<T>>(1));

    T* operator--(int) const noexcept;
    

    -4- Effects: Equivalent to: return static_cast<T>(static_cast<make_unsigned_t<T>>(fetch_sub(1)) - static_cast<make_unsigned_t<T>>(1));

  3. Modify [atomics.types.int] as indicated:

    T operator op=(T operand) volatile noexcept;
    T operator op=(T operand) noexcept;
    

    -8- Effects: Equivalent to: return static_cast<T>(static_cast<make_unsigned_t<T>>(fetch_key(operand)) op static_cast<make_unsigned_t<T>>(operand));

    [Drafting note: atomic<integral>'s working for operator++/operator-- is shared with atomic<T*>. — end drafting note]

    [Drafting note: atomic<floating-point> seems to be correct, LWG should confirm that it is. — end drafting note]

  4. Modify [atomics.types.pointer] as indicated:

    T* operator op=(ptrdiff_t operand) volatile noexcept;
    T* operator op=(ptrdiff_t operand) noexcept;
    

    -8- Effects: Equivalent to: return reinterpret_cast<T*>(reinterpret_cast<ptrdiff_t>(fetch_key(operand)) op operand);

    Remarks: The result may be an undefined address, but the operations otherwise have no undefined behavior.

  5. Modify [atomics.types.memop] as indicated:

    T operator++() volatile noexcept;
    T operator++() noexcept;
    

    -3- Effects: Equivalent to: return static_cast<T>(static_cast<make_unsigned_t<T>>(fetch_add(1)) + static_cast<make_unsigned_t<T>>(1));

    T operator--() volatile noexcept;
    T operator--() noexcept;
    

    -4- Effects: Equivalent to: return static_cast<T>(static_cast<make_unsigned_t<T>>(fetch_sub(1)) - static_cast<make_unsigned_t<T>>(1));

    [Drafting note: Alternatively, LWG may want to separate the integral overload of operator++/operator-- from that of atomic<T*>. end drafting note]

Date: 2018-01-29.17:21:16

[ 2018-01; Priority set to 3 after mailing list discussion ]

Date: 2017-12-15.00:00:00

Given atomic<int> meow{INT_MAX};, meow.fetch_add(1) has well-defined behavior because [atomics.types.int] p7 says that

Remarks: For signed integer types, arithmetic is defined to use two's complement representation. There are no undefined results.

but meow += 1 and ++meow have undefined behavior, because these operator functions are defined (by, respectively, [atomics.types.int] p8 and [atomics.types.memop]) to be equivalent to return fetch_add(1) + 1;, and so the addition of 1 to the result of fetch_add — which causes an integer overflow in this case — occurs outside the protection of fetch_add magic. Additionally, the return value might differ from what fetch_add actually wrote since that addition isn't required to use two's complement. This seems like a trap for the unwary. Is it intended?

A similar issue affects the atomic<T*> partial specialization for pointers.

History
Date User Action Args
2026-07-31 16:46:35adminsetmessages: + msg16541
2026-07-31 16:46:35adminsetstatus: new -> ready
2026-07-31 08:43:56adminsetmessages: + msg16534
2019-04-20 12:13:04adminsetmessages: + msg10396
2019-04-20 12:13:04adminsetmessages: + msg10395
2018-01-29 17:21:16adminsetmessages: + msg9662
2017-12-15 00:00:00admincreate