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.
atomic compound assignment operators can cause undefined behavior when corresponding
fetch_meow members don'tSection: 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
butRemarks: For signed integer types, arithmetic is defined to use two's complement representation. There are no undefined results.
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".
This wording is relative to N4810.
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));
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));
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:
[Drafting note:atomic<integral>'s working foroperator++/operator--is shared withatomic<T*>. — end drafting note]atomic<floating-point>seems to be correct, LWG should confirm that it is. — end drafting note]
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:
Remarks: The result may be an undefined address, but the operations otherwise have no undefined behavior.return reinterpret_cast<T*>(reinterpret_cast<ptrdiff_t>(fetch_key(operand)) op operand);
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 ofatomic<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.
Modify 32.5.7.3 [atomics.ref.int] as indicated:
constexpr value_type operator op=(value_type operand) const noexcept;-14- Constraints:
-15- Effects: Equivalent to:is_const_v<integral-type>isfalse.-16- Returns: The value written by the call toreturnfetch_key(operand)op operand;fetch_key.
Modify 32.5.7.4 [atomics.ref.float] as indicated:
constexpr value_type operator op=(value_type operand) const noexcept;-17- Constraints:
-18- Effects: Equivalent to:is_const_v<floating-point-type>isfalse.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 onfloating-point-typemay be different than the calling thread's floating-point environment.
Modify 32.5.7.5 [atomics.ref.pointer] as indicated:
constexpr value_type operator op=(difference_type operand) const noexcept;-14- Constraints:
-15- Effects: Equivalent to:is_const_v<pointer-type>isfalse.-16- Returns: The value written by the call toreturnfetch_key(operand)op operand;fetch_key.
Modify 32.5.7.6 [atomics.ref.memop] as indicated:
constexpr value_type operator++() const noexcept;-6- Constraints:
-7- Effects: Equivalent to:is_const_v<referred-type>isfalse.returnfetch_add(1) + 1operator+=(1);constexpr value_type operator--() const noexcept;-8- Constraints:
-9- Effects: Equivalent to:is_const_v<referred-type>isfalse.returnfetch_sub(1) + 1operator-=(1);
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
-15- Effects: Equivalent to:volatileoverload of this function,is_always_lock_freeistrue.-16- Returns: The value written by the call toreturnfetch_key(operand)op operand;fetch_key.
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
-18- Effects: Equivalent to:volatileoverload of this function,is_always_lock_freeistrue.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 onThe floating-point environment (29.3 [cfenv]) for atomic arithmetic operations onfloating-point-typeshould conform to thestd::numeric_limits<floating-point-type>traits associated with the floating-point type (17.3.3 [limits.syn]).floating-point-typemay be different than the calling thread's floating-point environment.
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
-16- Effects: Equivalent to:volatileoverload of this function,is_always_lock_freeistrue.-17- Returns: The value written by the call toreturnfetch_key(operand)op operand;fetch_key.
Modify 32.5.8.6 [atomics.types.memop] as indicated:
value_type operator++() volatile noexcept; constexpr value_type operator++() noexcept;-5- Constraints: For the
-6- Effects: Equivalent to:volatileoverload of this function,is_always_lock_freeistrue.returnfetch_add(1) + 1operator+=(1);value_type operator--() volatile noexcept; constexpr value_type operator--() noexcept;-7- Constraints: For the
-8- Effects: Equivalent to:volatileoverload of this function,is_always_lock_freeistrue.returnfetch_sub(1) + 1operator-=(1);