1. Revision History

1.1. r0 ➡ r1

Potential ABI breakage to achieve implementation efficiency was considered. SG1
is unanimously comfortable with this. Implementations can choose to avoid
breakage and offer a less efficient implementation.

LEWG was unhappy about atomic_signed_lock_free / atomic_unsigned_lock_free being optional. SG1 was worried that some platforms would be unable to implement
them as lock-free because they lack a compare-and-exchange instruction and might
not be able to disable interrupts on all cores. After discussion, SG1 agreed to
make these mandatory despite rare platforms potentially being unable to
implement these types.

SF

F

N

A

SA

atomic_signed_lock_free / atomic_unsigned_lock_free should be mandatory

4

9

12

3

1

Move this paper to LEWG with intent to include in IS, either with or without the change above

20

7

2

0

0

LEWG then saw the paper again, and there was unanimous consent to forward to LWG
for C++20.

1.2. Draft ➡ r0

This paper was written in Jacksonville and presented to SG1, which unanimously
forwarded the paper to LEWG. LEWG looked at the paper and took the following
poll:

SF

F

N

A

SA

Make the type aliases non-optional.

1

4

4

2

2

The types were made optional in case an architecture, such as PA-RISC, cannot
support always-lock-free integral types because no compare-and-exchange
instruction is available. There was no consensus for making aliases required,
though concern was expressed that LEWG doesn’t usually make functionality
optional. In the C++ standard library optionality is present as follows:

intN_t / uintN_t are mandated by C, "if an implementation provides integer
types with widths of 8, 16, 32, or 64 bits, no padding bits, and (for the
signed types) that have a two’s complement representation";

abs and div overloads "if and only if the type intmax_t designates an
extended integer type";

The library allocator_traits template has optional requirements.

There was also discussion about ABI breakage to atomic_flag. An argument was
made that atomic_flag should also be sized such that waiting on them is most
efficient (which would be an ABI breakage), and if that breakage doesn’t occur
then adding wait / notify overloads is actively misleading. LEWG want SG1 to
reconsider whether the overloads should be provided.

2. Introduction

C++11 added atomic_flag to the language as the minimally-required class which
could be used to implement atomic<> on hardware which seemed relevant at the
time. Detailed atomic_flag history can be found in [N2145], [N2324], and [N2393]. The specification was quite successful at minimalism—the only member
functions of atomic_flag are test_and_set and clear—but atomic<> was
wildly more successful and to our knowledge has always been implemented with
compiler support instead of with the very inefficient (but beautifully simple) atomic_flag. Our experience is that atomic_flag's interface is so minimal as
to be mostly useless, in particular it doesn’t have a method which can load the
flag’s value without modifying it.

We’ve heard of it being used as:

A questionable spinloop (as was originally intended);

A "check-in" flag used to know when at least one thread has reached a
program location.

The one special power atomic_flag has is in being the only type which is
guaranteed to be lock-free, albeit a mostly powerless one.

SG1 tried to salvage atomic_flag in [P0514R0] by adding set, test, wait, wait_until, and wait_for methods but decided to leave it as-is and
implement efficient waiting differently, eventually going for [P0514R3].

The time has come to thank atomic_flag for serving its purpose as an
implementability stand-in, and help it find its true purpose. We propose:

Adding a test method to it as [P0514R0] did. This could technically
forbids some ancestral processors from implementing modern C++, but these
platforms already don’t support any C++.

The type aliases atomic_intN_t, atomic_uintN_t, atomic_intptr_t, and atomic_uintptr_t are defined if and only if intN_t, uintN_t, intptr_t,
and uintptr_t are defined, respectively.

The type aliases atomic_signed_lock_free and atomic_unsigned_lock_free are
defined to be specializations of atomic whose template arguments are integral
types, respectively signed and unsigned, other than bool. is_always_lock_free shall be true for atomic_signed_lock_free and atomic_unsigned_lock_free. An implementation should choose the integral
specialization of atomic for which the waiting and notifying functions are
most efficient.

The atomic_flag type provides the classic test-and-set functionality. It has
two states, set and clear.

Operations on an object of type atomic_flag shall be lock-free. [ Note: Hence the operations should also be address-free. —end note]

The atomic_flag type is a standard-layout struct. It has a trivial default
constructor and a trivial destructor.

The macro ATOMIC_FLAG_INIT shall be defined in such a way that it can be used to initialize an object of
type atomic_flag to the clear state. The macro can be used in the form:

atomic_flagguard=ATOMIC_FLAG_INIT;

It is unspecified whether the macro can be used in other initialization
contexts. For a complete static-duration object, that initialization shall be
static. Unless initialized with ATOMIC_FLAG_INIT, it is unspecified whether an atomic_flag object has an initial state of set or clear.

The functions in this subclause provide a mechanism to wait for the value of an
atomic object to change, more efficiently than can be achieved with polling.
Waiting functions in this facility may block until they are unblocked by
notifying functions, according to each function’s effects. [Note: Programs
are not guaranteed to observe transient atomic values, an issue known as the
A-B-A problem, resulting in continued blocking if a condition is only
temporarily met. – End Note.]

The functions atomic_wait and atomic_wait_explicit are waiting
functions. The functions atomic_notify_one and atomic_notify_all are
notifying functions.

Effects: unblocks up to execution of a waiting function that blocked after
observing the result of an atomic operation X, if there exists another atomic
operation Y, such that X precedes Y in the modification order of *object, and
Y happens-before this call.

Effects: unblocks each execution of a waiting function that blocked after
observing the result of an atomic operation X, if there exists another atomic
operation Y, such that X precedes Y in the modification order of *object, and
Y happens-before this call.