This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Ready status.

3047. atomic compound assignment operators can cause undefined behavior when corresponding fetch_meow members don't

Section: 32.5.8.3 [atomics.types.int], 32.5.8.5 [atomics.types.pointer], 32.5.8.6 [atomics.types.memop] Status: Ready Submitter: Tim Song Opened: 2017-12-15 Last modified: 2026-07-31

Priority: 3

View all issues with Ready status.

Discussion:

Given atomic<int> meow{INT_MAX};, meow.fetch_add(1) has well-defined behavior because 32.5.8.3 [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, 32.5.8.3 [atomics.types.int] p8 and 32.5.8.6 [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.

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

[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".

Previous resolution [SUPERSEDED]

This wording is relative to N4810.

  1. Modify 32.5.7.3 [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 32.5.7.6 [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 32.5.8.3 [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 32.5.8.5 [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 32.5.8.6 [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]

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

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

Proposed resolution:

This wording is relative to N5054.

  1. Modify 32.5.7.3 [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 32.5.7.4 [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 (7.1 [expr.pre]) the result is unspecified, but the operations otherwise have no undefined behavior. The floating-point environment (29.3 [cfenv]) for atomic arithmetic operations on floating-point-type may be different than the calling thread's floating-point environment.

  3. Modify 32.5.7.5 [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 32.5.7.6 [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 32.5.8.3 [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 32.5.8.4 [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 (7.1 [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 (17.3.3 [limits.syn]). The floating-point environment (29.3 [cfenv]) for atomic arithmetic operations on floating-point-type may be different than the calling thread's floating-point environment.

  7. Modify 32.5.8.5 [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 32.5.8.6 [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);