This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
atomic
compound assignment operators can cause undefined behavior when corresponding
fetch_meow
members don'tSection: 33.5.8.3 [atomics.types.int], 33.5.8.5 [atomics.types.pointer], 33.5.8.6 [atomics.types.memop] Status: New Submitter: Tim Song Opened: 2017-12-15 Last modified: 2020-09-06
Priority: 3
View all issues with New status.
Discussion:
Given atomic<int> meow{INT_MAX};
, meow.fetch_add(1)
has well-defined behavior because 33.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,
33.5.8.3 [atomics.types.int] p8 and 33.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".
Proposed resolution:
This wording is relative to N4810.
Modify 33.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 33.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 33.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 33.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 33.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]