| Document #: | P2689R4 |
| Date: | 2026-10-13 |
| Project: | Programming Language C++ SG1 & LEWG |
| Reply-to: |
Christian Trott <crtrott@sandia.gov> Damien Lebrun-Grandie <lebrun-grandie@lanl.gov> Mark Hoemmen <mhoemmen@nvidia.com> Daniel Sunderland <dansunderland@gmail.com> Nic Morales <nmmoral@sandia.gov> Nevin Liber <nliber@anl.gov> |
atomic_ref changesatomic_ref
store_op member functionsvalue_type enabling a
const TU and T are
similar, and U* is convertible to T*)address functionatomic-ref-bounddifference_type in non-generic
atomic-ref-boundatomic-ref-bound omits
compare_exchange_{weak,strong} with user-specified
failure memory orderatomic-ref-bound actually
improves safety by making operations implicitly use a memory
order other than sequential consistencyLEWG reviewed P2689R3 on 2024-04-30 but did not have quorum.
LEWG reviewed P2689R3 again on 2026-08-04. This time, LEWG had quorum and took two POLLS.
“P2689 should provide conversions from different memory orders”: SF/F/N/A/SA, 0/0/2/8/1 (out of 16), consensus against.
“P2689 should make atomic-ref-bounded /
basic-atomic-accessor NON exposition only (user
spellable)”: SF/F/N/A/SA, 0/2/8/1/0 (out of 16), no consensus.
atomic-ref-bound into one
for arithmetic and pointer, and one for other types
difference_type should only
appear for arithmetic and pointer typesatomic-ref-bound
exposition only or notfetch_min and fetch_max
functionsatomic-ref-bounded to
atomic-ref-boundatomic-ref-unbounded to
atomic-ref-unboundbasic-atomic-accessor::offsetbasic-atomic-accessor::accessatomic-ref-bound as wellaccess() effects should be
return reference(p[i]); offset() effects should be return p+i;
| SF | F | N | A | SA |
|---|---|---|---|---|
| 5 | 9 | 0 | 0 | 0 |
Unanimous consent
| SF | F | N | A | SA |
|---|
No objection to unanimous consent
atomic-ref-bounded exposition-only
template for an atomic_ref like type bounded to a
particular memory_orderatomic_ref_relaxed,
atomic_ref_acq_rel and atomic_ref_seq_cst
alias templatesbasic-atomic-accessor exposition-only
templateatomic_accessor_relaxed,
atomic_accessor_acq_rel and
atomic_accessor_seq_cst templatesmemory_order versions of
atomic_ref (This says nothing about whether these are
aliases of general a template that takes a memory_order
argument.) atomic_ref_relaxed atomic_ref_acq_rel (acquire for load, release for store)
atomic_ref_seq_cst
| SF | F | N | A | SA |
|---|---|---|---|---|
| 1 | 11 | 0 | 1 | 0 |
Consensus
memory_order is that is the
default order memory_order
is that is the only order
| SX | X | N | Y | SY |
|---|---|---|---|---|
| 1 | 1 | 3 | 4 | 2 |
Consensus for Y
SX : we have a variant of this type in
Folly and it has been useful to be able to override this
SY : it’s
weird for atomic_ref_relaxed to have a seq_cst
atomic done to it; since you can re-form the _ref you can
still do it
atomic_accessor ->
atomic_ref atomic_accessor_relaxed ->
atomic_ref_relaxed atomic_accessor_acq_rel ->
atomic_ref_acq_rel atomic_accessor_seq_cst ->
atomic_ref_seq_cst | SF | F | N | A | SA |
|---|---|---|---|---|
| 6 | 6 | 2 | 0 | 0 |
This proposal adds three “bound atomic reference” types.
atomic_ref_seq_cst
atomic_ref_acq_rel
atomic_ref_relaxed
They work like atomic_ref, except that each of them is
bound to a particular memory_order value at compile time.
Wherever an atomic atomic_ref member function would have an
optional memory_order parameter with a default value, the
corresponding atomic_ref_* member function just uses its
class’ bound memory_order value.
The following table shows the memory order used by each of the three types for loads and stores.
| Atomic Ref |
memory_order
|
Loads | Stores |
|---|---|---|---|
atomic_ref_relaxed
|
memory_order_relaxed
|
memory_order_relaxed
|
memory_order_relaxed
|
atomic_ref_acq_rel
|
memory_order_acq_rel
|
memory_order_acquire
|
memory_order_release
|
atomic_ref_seq_cst
|
memory_order_seq_cst
|
memory_order_seq_cst
|
memory_order_seq_cst
|
This proposal also adds four mdspan “atomic accessors.”
Each of them performs element accesses atomically by using an atomic
reference type. They differ only by their reference
types.
| Accessor |
reference
|
|---|---|
atomic_accessor
|
atomic_ref
|
atomic_accessor_relaxed
|
atomic_ref_relaxed
|
atomic_accessor_acq_rel
|
atomic_ref_acq_rel
|
atomic_accessor_seq_cst
|
atomic_ref_seq_cst
|
The proposed accessors and their associated bound atomic references have two purposes.
They are vocabulary types that declare and restrict access intent at interface or object lifetime boundaries.
They are practically useful for making algorithms more readable.
The bound atomic references declare and restrict access intent for a
single object. For example, the following function
update_counter promises that it will only access
counter using sequentially consistent atomic updates. By
contrast, the interface of atomic_ref<int> permits
updates with any memory order.
void update_counter(atomic_ref_seq_cst<int> counter);The atomic accessors let users declare and restrict access intent for
the elements of an array. For example, the declaration below effectively
promises that the function update_array will only access
the elements of the array x using sequentially consistent
atomic updates.
void update_array(mdspan<float, dims<2>, layout_right, atomic_accessor_seq_cst<float>> x);The atomic accessors were part of the rationale provided in P0009,
the proposal that added mdspan to C++23, for
mdspan’s accessor policy template parameter.
The bound atomic references are called that because they bind a
memory order to all operations at compile time. This is particularly
useful for the overloaded arithmetic operators. All of
atomic_ref’s arithmetic operators must use
memory_order_seq_cst, since there is no way to pass in an
optional memory_order argument. The bound atomic references
give users both control over the memory order, and the convenient
syntax. Here is an example.
void compute_histogram(float bin_size,
std::span<int> output,
std::mdspan<float> data)
{
std::for_each(std::execution::par,
data.begin(), data.end(),
[=] (float val) {
int bin = std::abs(val)/bin_size;
if (bin > int(output.extent(0))) {
bin = output.extent(0) - 1;
}
std::atomic_ref_relaxed(accumulator[bin])++;
}
);
// ...
}A key use case for the atomic accessors is the ability to write
algorithms with generic mdspan outputs which can be called
in sequential and parallel contexts. When called in parallel contexts
users would simply pass an mdspan with an atomic accessor.
The algorithm implementation itself could be agnostic to the calling
context.
A variation on this use case is an implementation of an algorithm
taking an execution policy, which adds the atomic accessor to its output
argument if called with a parallel policy, while using the
default_accessor when called with a sequential policy. The
following demonstrates this with a function computing a histogram.
template<class T, class Extents, class LayoutPolicy>
constexpr auto
add_atomic_accessor_if_needed(std::execution::sequenced_policy,
std::mdspan<T, Extents, LayoutPolicy> m)
{
return m;
}
template<class ExecutionPolicy, class T, class Extents, class LayoutPolicy>
constexpr auto add_atomic_accessor_if_needed(ExecutionPolicy,
std::mdspan<T, Extents, LayoutPolicy> m)
{
return std::mdspan(m.data_handle(), m.mapping(), std::atomic_accessor<T>());
}
template<class ExecT>
void compute_histogram(ExecT exec, float bin_size,
std::mdspan<int, std::dims<1, int>> output,
std::mdspan<float, std::dims<1, int>> data)
{
static_assert(std::is_execution_policy_v<ExecT>);
auto accumulator = add_atomic_accessor_if_needed(exec, output);
std::for_each(exec,
data.data_handle(), data.data_handle() + data.extent(0),
[=] (float val) {
int bin = std::abs(val)/bin_size;
if (bin > int(output.extent(0))) {
bin = output.extent(0) - 1;
}
accumulator[bin]++;
}
);
}The above example is available on Compiler Explorer here.
Three options for atomic refs were discussed in SG1 in Kona 2022: add
new types for memory_order bound atomic refs, add a new
memory_order template parameter to the existing
atomic_ref, or add a constructor to the existing
atomic_ref that takes a memory_order and
stores it. Given that the last two are ABI breaks, the first option was
polled and chosen. It was also decided that the new bound atomic refs
would not support overriding the specified memory_order at
run time.
This proposal has chosen to make a general exposition-only template
atomic-ref-bound that takes a
memory_order as a template argument and alias templates for
the three specific bound atomic refs. Also, the various member functions
are constrained by integral types not including bool,
floating point types and pointer types, as opposed to the different
template specializations specified for atomic_ref. Other
than not being able to specifiy the memory_order at run
time, the intention is that the bound atomic ref types have the same
functionality and API as atomic_ref.
Similarly for the atomic accessors, it was decided in SG1 in Kona
2022 to add four new types. This proposal has chosen to make a general
exposition-only template basic-atomic-accessor
which takes the reference type as a template parameter, and
four alias templates for the specific atomic accessors.
Assuming both papers are approved, SG1 voted that similar changes to
atomic_ref in P2616R3 (Making std::atomic notification/wait
operations usable in more situations) should also be applied to
atomic-ref-bound. They are not yet in the wording
of either paper, as we do not know what order LWG will apply them to the
working draft.
As mentioned above, during SG1 review we introduced explicitly named
type aliases instead of making
basic-atomic-accessor and
atomic-ref-bound part of the public interface.
This is because algorithmic considerations generally dictate memory
order. It would be unusual to make an algorithm generic on the memory
order. The only generic thing one may want to decide is whether to use
atomics at all, e.g., as a function of the execution policy, in the
example above. We could not find a use case where the generic types are
useful enough to expose.
The design does not permit conversions between references or
accessors with different memory orders. This is because functions with
an atomic_ref_${MEMORY_ORDER} ref parameter strongly
suggest that they only access ref with the given
${MEMORY_ORDER}. Allowing conversions would make it easy to
violate that expectation.
For instance, consider a function
void foo_seq_cst(atomic_ref_seq_cst<T>). If
foo_seq_cst silently converts its input to
atomic_ref_relaxed<T>, that might break callers’
code, because correct use of relaxed atomics might require fences or
other synchronization.
Permitting the opposite conversion would not harm correctness, but it might harm performance. Consider the following example.
void foo_relaxed(atomic_ref_relaxed<int> counter);
void bar(int& counter) {
atomic_ref_relaxed atomic_counter(counter);
atomic_counter++;
...
foo(atomic_counter);
...
atomic_counter++;
atomic_thread_fence(memory_order::acq_rel);
}If users call foo_relaxed, they would reasonably expect
to need to use fences or other synchronization for correctness. If the
function actually uses sequentially consistent operations, then users
would pay for that synchronization unnecessarily. This is why we do not
permit conversions in the so-called “safer” direction.
Functions that might need to perform atomic operations with different
memory orders on the same memory location can use
atomic_ref<T> instead.
One argument for permitting conversions is that C++26 lets
users get the pointer out of an atomic_ref anyway, due to
the address member function (see P2835R7). That would let users
write the conversion by hand anyway, as the example below shows.
float x = 1.25f;
atomic_ref_seq_cst<float> x_ref(x);
// ...
atomic_ref_relaxed<float> x_ref2(
*reinterpret_cast<float*>(x_ref.address()));Our counterargument is that C++ lets users do all sorts of things
with reinterpret_cast. Some of those things are undefined
behavior. We don’t aim to prevent all possible ways users could abuse
the Standard Library; we just want to make it harder. Getting the object
address via address and calling
reinterpret_cast on it is a clear sign that the code is
doing something special and that readers need to pay close attention to
it. We would want such code to be explicit.
compare_exchange_{weak,strong} with user-specified
failure memory orderThe atomic_ref class template includes both
three-parameter and four-parameter versions of
compare_exchange_weak and
compare_exchange_strong. The four-parameter versions have
two separate memory_order parameters: success,
to use if the comparison is true, and failure, to use if
the comparison is false.
The three-parameter versions let users set the memory order on success, but bind the memory order on failure as a function of the success memory order, as specified in [atomics.ref.ops] 25.
When only one
memory_orderargument is supplied, the value ofsuccessisorder, and the value offailureisorderexcept that a value ofmemory_rder::acq_relshall be replaced by the valuememory_order::acquireand a value ofmemory_order::releaseshall be replaced by the valuememory_order::relaxed.
Given that the success memory order is the least
constrained, we define these functions in
atomic-ref-bound to use the class’ bound memory
order memory_ordering as the success memory
order, and to set the failure memory order
load_ordering based on the rule from [atomics.ref.ops] 25.
For example, we define two-parameter compare_exchange_weak
in atomic-ref-bound as follows.
constexpr bool compare_exchange_weak(
value_type& expected, value_type desired) const noexcept;Effects: Equivalent to:
ref.compare_exchange_weak(expected, desired, memory_ordering, load_ordering);
We do not define the following three-parameter analog that
would let users set the failure memory order
separately.
constexpr bool compare_exchange_weak(value_type& expected,
value_type desired, memory_order failure) const noexcept;This is because it would be too confusing for the third parameter to
be the success memory order for atomic_ref,
but to be the opposite failure memory order for
atomic-ref-bound. Users who want the functionality
of atomic_ref’s four-parameter overloads should just use
atomic_ref.
The basic-atomic-accessor class template looks
generic enough that it should work with many different kinds of proxy
references, not just atomic_ref and the bound atomic
references proposed here. Previous reviews suggested that we could
rename this class and make it available as a generic “proxy reference
accessor.”
We do not attempt to do this, because the Standard currently has no precedent for defining a “proxy reference” concept outside the context of iterators and Ranges. That would make a proxy reference accessor design original research, rather than a standardization of existing practice.
Our basic-atomic-accessor design is not as
generic as an arbitrary proxy reference accessor could be. First,
atomic_ref<T> and the bound atomic references
proposed here all wrap a core language reference T&.
Second, basic-atomic-accessor<T, Reference>
uses a raw pointer T* as its data_handle_type,
and gets the core language reference to wrap by accessing the pointer at
a given index. A fully generic proxy reference accessor would need
to
wrap an existing arbitrary accessor NestedAccessor
(that might have arbitrary data_handle_type,
reference, and offset_policy types);
dispatch to NestedAccessor::access (that might have
possibly arbitrary effects) in its access
function;
have a proxy reference that can wrap the possibly arbitrary
NestedAccessor::reference; and
dispatch to NestedAccessor::offset in its
offset function, and produce a data handle for a proxy
reference accessor that wraps the offset policy.
In order to design a generic “wrapping proxy reference,” we would need a proxy reference concept. It’s not obvious to us how to write that concept. Proxy references come in different forms. Sometimes they are just values. For example,
vector<bool>::const_reference is just
bool; and
linalg::scaled_accessor<SF, NA>::reference is
the result of multiplying two linear algebra value types (which could
either be another linear algebra value type, or an expression template
of one).
Sometimes a proxy reference is actually a tuple (as with
ranges_{enumerate,zip}_view) or pair (as with
flat_map).
Standard Algorithms concepts like indirectly_readable
and indirectly_writeable exist for iterators and Ranges.
The concepts always deal with iterators and reference types together.
For example, indirectly_readable constrains an iterator
type. The Standard does not describe proxy references by themselves.
The mdspan design does not depend on iterators or Ranges
machinery. That has trade-offs. For instance, the mdspan
authors wanted it to be easy for users who are not C++ experts to write
a custom accessor. Contrast that with writing a custom iterator, a
tricky enough task to motivate a Boost library and a proposal in flight,
P2727. It also avoids the
correctness and performance complications of attempting to define a
multidimensional iterator. However, it means that
mdspan-based algorithms need to build up their own concepts
and traits machinery.
As an example of such machinery, how would one write the analog of
views::as_const for mdspan algorithms? (This
question motivates P4311.) As a
part of that task, given a proxy reference type P, how
would one get a type P_c such that P is
convertible to P_c, and P_c is both
syntactically and semantically read-only? The “const version” of
atomic_ref<T> is just
atomic_ref<const T>. Not all proxy reference types
work like that, though. For vector<bool>::reference,
the const version is bool.
Even existing non-Ranges traits might not help us. For example,
common_reference_t of P and P_c
should be P_c. This works for
vector<bool>::reference. But for
atomic_ref, GCC 16.1 (-std=c++26 -Wall) and
MSVC (v19.51 VS18.6, /std::c++latest /W4) disagree with
Clang 22.1.0 (-std=c++26 -Wall) and nvc++ 26.5
(-std=c++23 -Wall). With GCC and MSVC,
common_reference_t<atomic_ref<float>, atomic_ref<const float>>
is atomic_ref<const float>, but with Clang and nvc++,
it is float. Please see the example below, which is
available in Compiler
Explorer.
#include <atomic>
#include <ranges>
#include <type_traits>
#include <vector>
#if defined(__clang__) || defined(__NVCOMPILER)
# define CUSTOMIZE_BASIC_COMMON_REFERENCE 1
#endif
#if defined(CUSTOMIZE_BASIC_COMMON_REFERENCE)
template <
class T,
class U,
template <class> class TQual,
template <class> class UQual
> requires std::same_as<U, std::remove_const_t<T>>
struct std::basic_common_reference<
std::atomic_ref<T>, std::atomic_ref<U>, TQual, UQual>
{
using type = std::atomic_ref<const T>;
};
template <
class T,
class U,
template <class> class TQual,
template <class> class UQual
> requires std::same_as<U, std::remove_const_t<T>>
struct std::basic_common_reference<
std::atomic_ref<U>, std::atomic_ref<T>, TQual, UQual>
{
using type = std::atomic_ref<const T>;
};
#endif
int main() {
// GCC and MSVC disagree with Clang and nvc++.
#if ! defined(__clang__) && ! defined(__NVCOMPILER)
// GCC says it's atomic_ref<const float>.
static_assert(std::is_same_v<
std::common_reference_t<std::atomic_ref<float>, std::atomic_ref<const float>>,
std::atomic_ref<const float>
>);
#endif
#if defined(__clang__) || defined(__NVCOMPILER)
# if defined(CUSTOMIZE_BASIC_COMMON_REFERENCE)
static_assert(std::is_same_v<
std::common_reference_t<std::atomic_ref<float>, std::atomic_ref<const float>>,
std::atomic_ref<const float>
>);
# else
static_assert(std::is_same_v<
std::common_reference_t<std::atomic_ref<float>, std::atomic_ref<const float>>,
float
>);
# endif
#endif
static_assert(std::is_same_v<
std::common_reference_t<
std::vector<bool>::reference, bool
>,
bool
>);
static_assert(std::is_same_v<
std::common_reference_t<
bool, std::vector<bool>::reference
>,
bool
>);
return 0;
}In LEWG’s review of R3 on 2026-08-04, one reviewer brought up a
concern that our bound atomic reference types introduce a “sharp edge”
that reduces safety, because they expose users to memory orders other
than sequential consistency. If users of atomic and
atomic_ref want a nondefault memory order, they must ask
for it explicitly on every atomic operation. Users of
atomic_ref_relaxed and atomic_ref_acq_rel
would get relaxed resp. acquire-release order on every atomic operation,
but the only time they would spell out the memory order would be in the
class name. The reviewer perceives this as less safe.
We disagree with the reviewer for the following reasons.
Our proposed bound atomic reference types are actually more safe
than atomic_ref when used as function parameters, because
atomic_ref lets the function’s implementation use any
memory order, while our bound reference types declare and constrain the
intended memory order at compile time.
Our proposed bound atomic accessor types make algorithms over arrays safer.
Atomic access itself is the sharpest edge.
atomic_refOur proposed bound reference types are actually more safe than the
existing atomic_ref when used as function parameters. This
is because our bound reference types declare and constrain the intended
memory order at compile time, while atomic_ref lets users
supply any memory order they like. This makes it harder to compose
functions with atomic_ref parameters. Consider the
following example.
void perform_update_0(atomic_ref<float>& ref, float input) {
ref.store(noncommutative_function(float(ref), input));
}
void perform_update_1(atomic_ref<float>& ref, float input) {
ref.store_add(input);
}
void apply_updates(atomic_ref<float>& ref, float in0, float in1) {
perform_update_0(ref, in0);
perform_update_1(ref, in1);
}The author of apply_updates likely expects the two
updates to happen in order. If we let x be the “original”
value of float(ref), then the expected outcome would be for
ref to hold
noncommutative_function(x, in0) + in1. However, if the
(possibly different) author of perform_update_0 and
perform_update_1 changes the memory order to relaxed, then
an execution of apply_updates might instead store
noncommutative_function(x + in1, in0). The author of
apply_updates would have no way to know by inspection of
the function declarations what memory order the functions use. If we
change the example to use atomic_ref_seq_cst<float>
instead of atomic_ref<float> as parameter types
throughout, then apply_updates can declare its required
memory order. Since we do not permit conversions between
atomic-ref-bound of different memory orders, users
would get a compilation order if this requirement were not
satisfied.
void perform_update_0(atomic_ref_seq_cst<float> ref, float input) {
ref.store(noncommutative_function(input));
}
void perform_update_1(atomic_ref_seq_cst<float> ref, float input) {
ref.store_add(input);
}
void apply_updates(atomic_ref_seq_cst<float> ref, float in0, float in1) {
perform_update_0(ref, in0);
perform_update_1(ref, in1);
}The Kokkos project has
over a decade of practical experience with atomic_ref_*
analogs. Users almost exclusively access them through Kokkos’ analog of
mdspan in parallel algorithms. They almost always do so
through overloaded arithmetic operators in computations that are
mathematically associative and commutative. This fits the intended use
case of atomic_ref as explained in P0019.
Users aren’t building complicated lock-free algorithms with these
reference types. They hardly ever create or return one explicitly or
spell out their names.
Idiomatic mdspan-based algorithm interfaces help lead to
safe use of our bound atomic reference types. Algorithms declare their
access intent through the template arguments of their
mdspan parameters. If an mdspan parameter has
accessor atomic_accessor_seq_cst, for example, then the
algorithm will only perform sequentially consistent atomic updates to
the elements of that mdspan. This is the array analog of a
function with an atomic_ref_seq_cst parameter.
Our atomic accessor types thus work together with our bound reference
types to improve safety of array-based algorithm interfaces. P0019
explains that the intent of atomic_ref is to support
“phases” of computation that alternate between parallel algorithms with
atomic accesses, and parallel algorithms with nonatomic accesses. Our
atomic accessor types let the atomic phases declare their array access
intent. Each phase’s implementation then does not need to create
atomic_ref or atomic-ref-bound
objects explicitly.
The C++ Standard includes parallel algorithms like
std::ranges::for_each. For parallel_policy and
parallel_unsequenced_policy, the Standard parallel
algorithms promise at most parallel forward progress across function
invocations. This means that if users might experience deadlock if they
try to use blocking synchronization. The same might happen with
explicitly user-created thread or jthread
threads, because implementations are not required to promise concurrent
forward progress. Both atomic and atomic_ref
make it easy for users to implement synchronization by hand, perhaps
without even meaning to. For example, users can create a barrier without
even knowing what a barrier is, by incrementing a counter atomically and
spinning until the counter reaches some value. Software developers need
tools like thread sanitizers to find such cases. Thus, we do not think
of sequential consistency as necessarily “safer.” Users still need to
understand forward progress guarantees and deadlock in order to use
atomic operations correctly.
An atomic_ref instance wraps an existing object. This
already makes it possible for users to invoke undefined behavior by
accessing the object nonatomically while atomic accesses are still in
flight. It’s still undefined behavior even if all those atomic accesses
use sequentially consistent order. P0019
explains why we accept this sharper interface. The main intended use
case of atomic_ref is to support atomic operations on
elements of existing array allocations. Without atomic_ref,
those applications would need to use arrays of
atomic<T> instead of arrays of T. That
would result in a serious performance penalty for phases of computation
where atomic access is unnecessary.
This proposal uses alias templates to exposition-only types for
atomic_ref_relaxed, atomic_accessor, etc.
However, we do not want to prescribe a particular implementation. For
instance, if an implementer wished to derive from a type like
atomic-ref-bound (to get more user-friendly name
mangling, which is something not normally covered by the Standard),
would they be allowed to do so? We believe an alias template to an
exposition-only type is not observable from the point of view of the
Standard and such an implementation would be allowed, but we request
clarification on this. Potentially this is something we can leave to LWG
review to suggest wording that would achieve that goal.
The proposed changes are relative to N5054:
[ Editor's note: Intent
is for atomic_ref_* to be freestanding. We don’t need to
mark this because [atomics.syn] is already marked “mostly freestanding.”
]
// [atomics.ref.pointer], class template atomic_ref
template<class T> struct atomic_ref<T>;// [atomics.ref.bounded], class template atomic-ref-bound]
template<class T, class MemoryOrder> atomic-ref-bound; // exposition only
template<class T> using atomic_ref_relaxed = atomic-ref-bound<T, memory_order_relaxed>;
template<class T> using atomic_ref_acq_rel = atomic-ref-bound<T, memory_order_acq_rel>;
template<class T> using atomic_ref_seq_cst = atomic-ref-bound<T, memory_order_seq_cst>;
// [atomics.types.generic], class template atomic
template<class T> struct atomic<T>;
// [atomics.types.pointer], partial specialization for pointers
template<class T> struct atomic<T*>;Class template atomic-ref-bound
[atomics.refbound]
Exposition only helper [atomics.refbound.helpers]
template<class T>
constexpr bool atomic-ref-non-generic-type();Returns: true if the class
atomic_ref<T> has a member type definition
difference_type, otherwise false.
[Note: This is true for a set of defined integral types, floating point types, and pointers.]
Generic atomic-ref-bound
[atomics.refbound.generic]
Overview [atomics.refbound.generic.overview]
template <class T, memory_order MemoryOrder>
struct atomic-ref-bound { // exposition only
private:
using atomic-ref-unbound = atomic_ref<T>; // exposition only
atomic-ref-unbound ref; // exposition only
static constexpr memory_order store_ordering =
MemoryOrder == memory_order_acq_rel ? memory_order_release
: MemoryOrder; // exposition only
static constexpr memory_order load_ordering =
MemoryOrder == memory_order_acq_rel ? memory_order_acquire
: MemoryOrder; // exposition only
template<class T> friend class atomic-ref-bound<T, MemoryOrder>;
public:
using value_type = remove_cv_t<T>;
static constexpr memory_order memory_ordering = MemoryOrder;
static constexpr size_t required_alignment = atomic-ref-unbound::required_alignment;
static constexpr bool is_always_lock_free = atomic-ref-unbound::is_always_lock_free;
bool is_lock_free() const noexcept;
constexpr explicit atomic-ref-bound(T&);
explicit atomic-ref-bound(T&&) = delete;
constexpr atomic-ref-bound(const atomic-ref-bound&) noexcept;
template<class U>
constexpr atomic-ref-bound(const atomic-ref-bound<U, memory_ordering>&) noexcept;
atomic-ref-bound& operator=(const atomic-ref-bound&) = delete;
constexpr void store(value_type desired) const noexcept;
constexpr value_type operator=(value_type desired) const noexcept;
constexpr value_type load() const noexcept;
constexpr operator value_type() const noexcept;
constexpr value_type exchange(value_type desired) const noexcept;
constexpr bool compare_exchange_weak(value_type& expected, value_type desired) const noexcept;
constexpr bool compare_exchange_strong(value_type& expected, value_type desired) const noexcept;
constexpr void wait(value_type old) const noexcept;
constexpr void notify_one() const noexcept;
constexpr void notify_all() const noexcept;
constexpr auto address() const noexcept;
};1
Class atomic-ref-bound is for exposition only.
2 Mandates:
is_trivially_copyable_v<T> is true.Operations [atomics.refbound.generic.ops]
bool is_lock_free() const noexcept;1
Effects: Equivalent to: return
ref.is_lock_free();
constexpr explicit atomic-ref-bound(T& t);2
Preconditions: The referenced object is aligned to
required_alignment.
3
Postconditions: ref references the object
referenced by t.
4 Throws: Nothing.
constexpr atomic-ref-bound(const atomic-ref-bound& other) noexcept;5
Effects: Direct-non-list-initializes ref
with other.ref.
template<class U>
constexpr atomic-ref-bound(const atomic-ref-bound<U>& other) noexcept;6 Constraints:
7
Effects: Direct-non-list-initializes ref
with other.ref.
constexpr void store(value_type desired) const noexcept;8
Constraints: is_const_v<T> is
false.
9
Effects: Equivalent to:
ref.store(desired, store_ordering);
constexpr value_type operator=(value_type desired) const noexcept;10
Constraints: is_const_v<T> is
false.
11 Effects: Equivalent to:
store(desired);
return desired;`constexpr value_type load() const noexcept;12
Effects: Equivalent to: return
ref.load(load_ordering);
constexpr operator value_type() const noexcept;13
Effects: Equivalent to: return load();
constexpr value_type exchange(value_type desired) const noexcept;14
Constraints: is_const_v<T> is
false.
15
Effects: Equivalent to: return
ref.exchange(desired, memory_ordering);
constexpr bool compare_exchange_weak(value_type& expected, value_type desired) const noexcept;16
Constraints: is_const_v<T> is
false.
17
Effects: Equivalent to: return
ref.compare_exchange_weak(expected, desired, memory_ordering, load_ordering);
constexpr bool compare_exchange_strong(value_type& expected, value_type desired) const noexcept;18
Constraints: is_const_v<T> is
false.
19
Effects: Equivalent to: return
ref.compare_exchange_strong(expected, desired, memory_ordering, load_ordering);
constexpr void wait(value_type old) const noexcept;20
Effects: Equivalent to:
ref.wait(old, load_ordering);
constexpr void notify_one() const noexcept;21
Constraints: is_const_v<T> is
false.
22
Effects: Equivalent to:
ref.notify_one();
constexpr void notify_all() const noexcept;23
Constraints: is_const_v<T> is
false.
24
Effects: Equivalent to:
ref.notify_all();
constexpr auto address() const noexcept;25
Effects: Equivalent to: return
ref.address();
Non-generic atomic-ref-bound
[atomics.refbound.nongeneric]
Overview [atomics.refbound.nongeneric.overview]
template <class T, memory_order MemoryOrder>
requires(atomic-ref-non-generic-type<T>())
struct atomic-ref-bound<T, MemoryOrder> { // exposition only
private:
using atomic-ref-unbound = atomic_ref<T>; // exposition only
atomic-ref-unbound ref; // exposition only
static constexpr memory_order store_ordering =
MemoryOrder == memory_order_acq_rel ? memory_order_release
: MemoryOrder; // exposition only
static constexpr memory_order load_ordering =
MemoryOrder == memory_order_acq_rel ? memory_order_acquire
: MemoryOrder; // exposition only
template<class T> friend class atomic-ref-bound<T, MemoryOrder>;
public:
using value_type = remove_cv_t<T>;
private:
static constexpr bool is-integral-value = // exposition only
is_integral_v<value_type> && !is_same_v<value_type, bool>;
static constexpr bool is-floating-point-value = // exposition only
is_floating_point_v<value_type>;
static constexpr bool is-pointer-value = // exposition only
is_pointer_v<value_type>;
public:
using difference_type = atomic-ref-unbound::difference_type;
static constexpr memory_order memory_ordering = MemoryOrder;
static constexpr size_t required_alignment =
atomic-ref-unbound::required_alignment;
static constexpr bool is_always_lock_free =
atomic-ref-unbound::is_always_lock_free;
// [atomics.refbound.nongeneric.ops] Operations common with generic version
bool is_lock_free() const noexcept;
constexpr explicit atomic-ref-bound(T& t);
explicit atomic-ref-bound(T&&) = delete;
template<class U>
constexpr atomic-ref-bound(const atomic-ref-bound<U, memory_ordering>&) noexcept;
constexpr atomic-ref-bound(const atomic-ref-bound&) noexcept;
constexpr atomic-ref-bound& operator=(const atomic-ref-bound&) = delete;
constexpr void store(value_type desired) const noexcept;
constexpr value_type operator=(value_type desired) const noexcept;
constexpr value_type load() const noexcept;
constexpr operator value_type() const noexcept;
constexpr value_type exchange(value_type desired) const noexcept;
constexpr bool compare_exchange_weak(value_type& expected, value_type desired) const noexcept;
constexpr bool compare_exchange_strong(value_type& expected, value_type desired) const noexcept;
constexpr void wait(value_type old) const noexcept;
constexpr void notify_one() const noexcept;
constexpr void notify_all() const noexcept;
constexpr auto address() const noexcept;
// [atomics.refbound.nongeneric.common] Common operations
constexpr value_type fetch_add(difference_type operand) const noexcept
constexpr value_type fetch_sub(difference_type operand) const noexcept;
constexpr value_type fetch_max(value_type operand) const noexcept;
constexpr value_type fetch_min(value_type operand) const noexcept;
constexpr void store_add(difference_type operand) const noexcept;
constexpr void store_sub(difference_type operand) const noexcept;
constexpr void store_max(value_type operand) const noexcept;
constexpr void store_min(value_type operand) const noexcept;
constexpr value_type operator+=(difference_type operand) const noexcept;
constexpr value_type operator-=(difference_type operand) const noexcept;
// [atomics.refbound.nongeneric.integral] Integral only operations
constexpr value_type fetch_and(value_type operand) const noexcept;
constexpr value_type fetch_or (value_type operand) const noexcept;
constexpr value_type fetch_xor(value_type operand) const noexcept;
constexpr void store_and(value_type operand) const noexcept;
constexpr void store_or (value_type operand) const noexcept;
constexpr void store_xor(value_type operand) const noexcept;
constexpr value_type operator&=(value_type operand) const noexcept;
constexpr value_type operator|=(value_type operand) const noexcept;
constexpr value_type operator^=(value_type operand) const noexcept;
// [atomics.refbound.nongeneric.floatingpoint] Floating point only operations
constexpr value_type fetch_fmaximum(value_type operand) const noexcept;
constexpr value_type fetch_fminimum(value_type operand) const noexcept;
constexpr value_type fetch_fmaximum_num(value_type operand) const noexcept;
constexpr value_type fetch_fminimum_num(value_type operand) const noexcept;
constexpr void store_fmaximum(value_type operand) const noexcept;
constexpr void store_fminimum(value_type operand) const noexcept;
constexpr void store_fmaximum_num(value_type operand) const noexcept;
constexpr void store_fminimum_num(value_type operand) const noexcept;
// [atomics.refbound.nongeneric.integralpointer] Integral and pointer type operations
constexpr value_type operator++(int) const noexcept;
constexpr value_type operator++() const noexcept;
constexpr value_type operator--(int) const noexcept;
constexpr value_type operator--() const noexcept;
};1
Class atomic-ref-bound is for exposition only.
2 Mandates:
is_trivially_copyable_v<T> is true.[Note: Unlike atomic_ref, the memory ordering for
the arithmetic operators is MemoryOrder, which is not
necessarily memory_order_seq_cst. – end note]
Generic Operations [atomics.refbound.nongeneric.ops]
bool is_lock_free() const noexcept;1
Effects: Equivalent to: return
ref.is_lock_free();
constexpr explicit atomic-ref-bound(T& t);2
Preconditions: The referenced object is aligned to
required_alignment.
3
Effects: Direct-non-list-initializes ref
with t.
4 Throws: Nothing.
constexpr atomic-ref-bound(const atomic-ref-bound& other) noexcept;7
Effects: Direct-non-list-initializes ref with
other.ref.
template<class U>
constexpr atomic-ref-bound(const atomic-ref-bound<U>& other) noexcept;6 Constraints:
7
Effects: Direct-non-list-initializes ref
with other.ref.
constexpr void store(value_type desired) const noexcept;8
Constraints: is_const_v<T> is
false.
9
Effects: Equivalent to:
ref.store(desired, store_ordering);
constexpr value_type operator=(value_type desired) const noexcept;10
Constraints: is_const_v<T> is
false.
11 Effects: Equivalent to:
store(desired);
return desired;`constexpr value_type load() const noexcept;12
Effects: Equivalent to: return
ref.load(load_ordering);
constexpr operator value_type() const noexcept;13
Effects: Equivalent to: return load();
constexpr value_type exchange(value_type desired) const noexcept;14
Constraints: is_const_v<T> is
false.
15
Effects: Equivalent to: return
ref.exchange(desired, memory_ordering);
constexpr bool compare_exchange_weak(value_type& expected, value_type desired) const noexcept;16
Constraints: is_const_v<T> is
false.
17
Effects: Equivalent to: return
ref.compare_exchange_weak(expected, desired, memory_ordering, load_ordering);
constexpr bool compare_exchange_strong(value_type& expected, value_type desired) const noexcept;18
Constraints: is_const_v<T> is
false.
19
Effects: Equivalent to: return
ref.compare_exchange_strong(expected, desired, memory_ordering, load_ordering);
constexpr void wait(T old) const noexcept;20
Effects: Equivalent to:
ref.wait(old, load_ordering);
constexpr void notify_one() const noexcept;21
Constraints: is_const_v<T> is
false.
22
Effects: Equivalent to:
ref.notify_one();
constexpr void notify_all() const noexcept;23
Constraints: is_const_v<T> is
false.
24
Effects: Equivalent to:
ref.notify_all();
constexpr auto address() const noexcept;25
Effects: Equivalent to: return
ref.address();
Common Operations [atomics.refbound.nongeneric.common]
constexpr value_type fetch_add(difference_type operand) const noexcept;1
Constraints: is_const_v<T> is
false.
2
Effects: Equivalent to: return
ref.fetch_add(operand, memory_ordering);
constexpr value_type fetch_sub(difference_type operand) const noexcept;3
Constraints: is_const_v<T> is
false.
4
Effects: Equivalent to: return
ref.fetch_sub(operand, memory_ordering);
constexpr value_type fetch_max(value_type operand) const noexcept;5
Constraints: is_const_v<T> is
false.
6
Effects: Equivalent to: return
ref.fetch_max(operand, memory_ordering);
constexpr value_type fetch_min(value_type operand) const noexcept;7
Constraints: is_const_v<T> is
false.
8
Effects: Equivalent to: return
ref.fetch_min(operand, memory_ordering);
constexpr void store_add(difference_type operand) const noexcept;9
Constraints: is_const_v<T> is
false.
10
Effects: Equivalent to:
ref.store_add(operand, store_ordering);
constexpr void store_sub(difference_type operand) const noexcept;11
Constraints: is_const_v<T> is
false.
12
Effects: Equivalent to:
ref.store_sub(operand, store_ordering);
constexpr void store_max(value_type operand) const noexcept;13
Constraints: is_const_v<T> is
false.
14
Effects: Equivalent to:
ref.store_max(operand, store_ordering);
constexpr void store_min(value_type operand) const noexcept;15
Constraints: is_const_v<T> is
false.
16
Effects: Equivalent to:
ref.store_min(operand, store_ordering);
constexpr value_type operator+=(difference_type operand) const noexcept;17
Constraints: is_const_v<T> is
false.
18
Effects: Equivalent to:
return fetch_add(operand) + operand;
constexpr value_type operator-=(difference_type operand) const noexcept;19
Constraints: is_const_v<T> is
false.
20
Effects: Equivalent to:
return fetch_sub(operand) - operand;
Integral Operations [atomics.refbound.nongeneric.integral]
constexpr value_type fetch_and(value_type operand) const noexcept;1
Constraints: is-integral-value
&& !is_const_v<T> is true.
2
Effects: Equivalent to: return
ref.fetch_and(operand, memory_ordering);
constexpr value_type fetch_or(value_type operand) const noexcept;3
Constraints: is-integral-value
&& !is_const_v<T> is true.
4
Effects: Equivalent to: return
ref.fetch_or(operand, memory_ordering);
constexpr value_type fetch_xor(value_type operand) const noexcept;5
Constraints: is-integral-value
&& !is_const_v<T> is true.
6
Effects: Equivalent to: return
ref.fetch_xor(operand, memory_ordering);
constexpr void store_and(value_type operand) const noexcept;7
Constraints: is-integral-value
&& !is_const_v<T> is true.
8
Effects: Equivalent to:
ref.store_and(operand, store_ordering);
constexpr void store_or(value_type operand) const noexcept;9
Constraints: is-integral-value
&& !is_const_v<T> is true.
10
Effects: Equivalent to:
ref.store_or(operand, store_ordering);
constexpr void store_xor(value_type operand) const noexcept;11
Constraints: is-integral-value
&& !is_const_v<T> is true.
12
Effects: Equivalent to:
ref.store_xor(operand, store_ordering);
constexpr value_type operator&=(value_type operand) const noexcept;13
Constraints: is-integral-value
&& !is_const_v<T> is true.
14
Effects: Equivalent to:
return fetch_and(operand) & operand;
constexpr value_type operator|=(value_type operand) const noexcept;15
Constraints: is-integral-value
&& !is_const_v<T> is true.
16
Effects: Equivalent to:
return fetch_or(operand) | operand;
constexpr value_type operator^=(value_type operand) const noexcept;17
Constraints: is-integral-value
&& !is_const_v<T> is true.
18
Effects: Equivalent to:
return fetch_xor(operand) ^ operand;
Floating Point Operations [atomics.refbound.nongeneric.floatingpoint]
constexpr value_type fetch_fmaximum(value_type operand) const noexcept;1
Constraints: is-floating-point-value
&& !is_const_v<T> is true.
2
Effects: Equivalent to: return
ref.fetch_fmaximum(operand, memory_ordering);
constexpr value_type fetch_fminimum(value_type operand) const noexcept;3
Constraints: is-floating-point-value
&& !is_const_v<T> is true.
4
Effects: Equivalent to: return
ref.fetch_fminimum(operand, memory_ordering);
constexpr value_type fetch_fmaximum_num(value_type operand) const noexcept;5
Constraints: is-floating-point-value
&& !is_const_v<T> is true.
6
Effects: Equivalent to: return
ref.fetch_fmaximum_num(operand, memory_ordering);
constexpr value_type fetch_fminimum_num(value_type operand) const noexcept;7
Constraints: is-floating-point-value
&& !is_const_v<T> is true.
8
Effects: Equivalent to: return
ref.fetch_fminimum_num(operand, memory_ordering);
constexpr void store_fmaximum(value_type operand) const noexcept;9
Constraints: is-floating-point-value
&& !is_const_v<T> is true.
10
Effects: Equivalent to:
ref.store_fmaximum(operand, store_ordering);
constexpr void store_fminimum(value_type operand) const noexcept;11
Constraints: is-floating-point-value
&& !is_const_v<T> is true.
12
Effects: Equivalent to:
ref.store_fminimum(operand, store_ordering);
constexpr void store_fmaximum_num(value_type operand) const noexcept;13
Constraints: is-floating-point-value
&& !is_const_v<T> is true.
14
Effects: Equivalent to:
ref.store_fmaximum_num(operand, store_ordering);
constexpr void store_fminimum_num(value_type operand) const noexcept;15
Constraints: is-floating-point-value
&& !is_const_v<T> is true.
16
Effects: Equivalent to:
ref.store_fminimum_num(operand, store_ordering);
Integral and Pointer Operations [atomics.refbound.nongeneric.integralpointer]
constexpr value_type operator++(int) const noexcept;1 Constraints:
is-integral-value ||
is-pointer-value is true,
and
is_const_v<T> is false.
2
Effects: Equivalent to: return fetch_add(1);
constexpr value_type operator++() const noexcept;3 Constraints:
is-integral-value ||
is-pointer-value is true,
and
is_const_v<T> is false.
4
Effects: Equivalent to:
return fetch_add(1) + 1;
constexpr value_type operator--(int) const noexcept;5 Constraints:
is-integral-value ||
is-pointer-value is true,
and
is_const_v<T> is false.
6
Effects: Equivalent to: return fetch_sub(1);
constexpr value_type operator--() const noexcept;7 Constraints:
is-integral-value ||
is-pointer-value is true,
and
is_const_v<T> is false.
8
Effects: Equivalent to:
return fetch_sub(1) - 1;
Memory Order Specific Atomic Refs [atomics.refbound.order]
// all freestanding
namespace std {
template<class T>
using atomic_ref_relaxed = atomic-ref-bound<T, memory_order_relaxed>;
template<class T>
using atomic_ref_acq_rel = atomic-ref-bound<T, memory_order_acq_rel>;
template<class T>
using atomic_ref_seq_cst = atomic-ref-bound<T, memory_order_seq_cst>;
}Update the feature test macro __cpp_lib_atomic_ref.
// [mdspan.accessor.default], class template default_accessor
template<class ElementType>
class default_accessor;
// [mdspan.accessor.aligned], class template aligned_accessor
template<class ElementType, size_t ByteAlignment>
class aligned_accessor;// [atomics.accessor.atomic], class template basic-atomic-accessor
template<class T, class ReferenceType> class basic-atomic-accessor; // exposition only
template<class T> using atomic_accessor = basic-atomic-accessor<T, atomic_ref<T>>;
template<class T> using atomic_accessor_relaxed = basic-atomic-accessor<T, atomic_ref_relaxed<T>>;
template<class T> using atomic_accessor_acq_rel = basic-atomic-accessor<T, atomic_ref_acq_rel<T>>;
template<class T> using atomic_accessor_seq_cst = basic-atomic-accessor<T, atomic_ref_seq_cst<T>>;// [mdspan.mdspan], class template mdspan
template<class ElementType, class Extents, class LayoutPolicy = layout_right,
class AccessorPolicy = default_accessor<ElementType>>
class mdspan;Class template basic-atomic-accessor
[mdspan.accessor.atomic.basic]
General [mdspan.accessor.atomic.basic.overview]
template <class ElementType, class ReferenceType>
struct basic-atomic-accessor { // exposition only
using offset_policy = basic-atomic-accessor;
using element_type = ElementType;
using reference = ReferenceType;
using data_handle_type = ElementType*;
constexpr basic-atomic-accessor() noexcept = default;
template <class OtherElementType>
constexpr basic-atomic-accessor(default_accessor<OtherElementType>) noexcept;
template <class OtherElementType>
constexpr basic-atomic-accessor(basic-atomic-accessor<OtherElementType, ReferenceType>) noexcept;
constexpr reference access(data_handle_type p, size_t i) const noexcept;
constexpr data_handle_type offset(data_handle_type p, size_t i) const noexcept;
};1
Class basic-atomic-accessor is for exposition
only.
2
basic-atomic-accessor meets the accessor policy
requirements.
3
ElementType is required to be a complete object type that
is neither an abstract class type nor an array type.
4
Each specialization of basic-atomic-accessor is a
trivially copyable type that models semiregular.
5
[0, n) is an accessible range for an object p
of type data_handle_type and an object of type
basic-atomic-accessor if and only if
[p, p+n) is a valid range.
Members [mdspan.accessor.atomic.basic.members]
template <class OtherElementType>
constexpr basic-atomic-accessor(default_accessor<OtherElementType>) noexcept {}
template <class OtherElementType>
constexpr basic-atomic-accessor(basic-atomic-accessor<OtherElementType, ReferenceType>) noexcept {}1
Constraints:
is_convertible_v<OtherElementType (*)[], element_type (*)[]>
is true.
constexpr reference access(data_handle_type p, size_t i) const noexcept;2
Effects: Equivalent to return reference(p[i]);
constexpr data_handle_type offset(data_handle_type p, size_t i) const noexcept;3
Effects: Equivalent to return p + i;
Atomic accessors [mdspan.accessor.atomic.bounded]
namespace std {
template <class ElementType>
using atomic_accessor = basic-atomic-accessor<ElementType, atomic_ref<ElementType>>;
template <class ElementType>
using atomic_accessor_relaxed = basic-atomic-accessor<ElementType, atomic_ref_relaxed<ElementType>>;
template <class ElementType>
using atomic_accessor_acq_rel = basic-atomic-accessor<ElementType, atomic_ref_acq_rel<ElementType>>;
template <class ElementType>
using atomic_accessor_seq_cst = basic-atomic-accessor<ElementType, atomic_ref_seq_cst<ElementType>>;
}Add the following feature test macro:
#define __cpp_lib_atomic_accessors YYYYMML // also in <mdspan>Sandia National Laboratories is a multimission laboratory managed and operated by National Technology and Engineering Solutions of Sandia, LLC., a wholly owned subsidiary of Honeywell International, Inc., for the U.S. Department of Energy’s National Nuclear Security Administration under Grant DE-NA-0003525.
This manuscript has been authored by UTBattelle, LLC, under Grant DE-AC05-00OR22725 with the U.S. Department of Energy (DOE).
This work was supported by Exascale Computing Project 17-SC-20-SC, a joint project of the U.S. Department of Energy’s Office of Science and National Nuclear Security Administration, responsible for delivering a capable exascale ecosystem, including software, applications, and hardware technology, to support the nation’s exascale computing imperative.
This research used resources of the Argonne Leadership Computing Facility, which is a DOE Office of Science User Facility supported under Contract DE-AC02-06CH11357.