Atomic Refs Bound to Memory Orderings & Atomic Accessors

Document #: P2689R4
Date: 2026-10-13
Project: Programming Language C++
SG1 & LEWG
Reply-to: Christian Trott
<>
Damien Lebrun-Grandie
<>
Mark Hoemmen
<>
Daniel Sunderland
<>
Nic Morales
<>
Nevin Liber
<>

1 Revision History

1.1 P2689R4 LEWG Feedback and catching up to atomic_ref changes

1.2 LEWG reviews of R3

1.3 P2689R3 (LEWG Feedback)

1.4 P2689R2 (SG1 Issaquah 2023 discussion)

1.4.1 Issaquah 2023 SG1 Polls

1.4.1.1 P2689 poll

Forward P2689R1 to LEWG, targeting C++26 Incl. wording changes:
- Atomic ref [un]bounded to [un]bound
- Basic atomic accessors access() effects should be return reference(p[i]);
- Basic atomic accessors offset() effects should be return p+i;
SF F N A SA
5 9 0 0 0

Unanimous consent

1.4.1.2 P2616 poll

Apply changes in P2616R3 to the additions of P2689R1
SF F N A SA

No objection to unanimous consent

1.5 P2689R1 2023-01 (pre-Issaquah 2023) mailing

1.6 Initial Version 2022-10 Mailing

1.6.1 Kona 2022 SG1 polls

1.6.1.1 SG1 Poll #1

We like the bound memory_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

1.6.1.2 SG1 Poll #2

X) The meaning of the bound memory_order is that is the default order
Y) The meaning of the bound 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

1.6.1.3 SG1 Poll #3

the next version of this proposal, with these changes, should target SG1 & LEWG

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

(Return to SG1 for final wording when this is in LWG.)
SF F N A SA
6 6 2 0 0

2 Summary

This proposal adds three “bound atomic reference” types.

  1. atomic_ref_seq_cst

  2. atomic_ref_acq_rel

  3. 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

3 Rationale

The proposed accessors and their associated bound atomic references have two purposes.

  1. They are vocabulary types that declare and restrict access intent at interface or object lifetime boundaries.

  2. 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.

4 Design decisions

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.

4.1 Generic reference and accessor are exposition only

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.

4.2 No memory order conversions

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.

4.3 Omit compare_exchange_{weak,strong} with user-specified failure memory order

The 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_order argument is supplied, the value of success is order, and the value of failure is order except that a value of memory_rder::acq_rel shall be replaced by the value memory_order::acquire and a value of memory_order::release shall be replaced by the value memory_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.

4.4 No generic “proxy reference” accessor

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

  1. wrap an existing arbitrary accessor NestedAccessor (that might have arbitrary data_handle_type, reference, and offset_policy types);

  2. dispatch to NestedAccessor::access (that might have possibly arbitrary effects) in its access function;

  3. have a proxy reference that can wrap the possibly arbitrary NestedAccessor::reference; and

  4. 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,

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;
}

4.5 Why this proposal improves safety

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.

  1. 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.

  2. Our proposed bound atomic accessor types make algorithms over arrays safer.

  3. Atomic access itself is the sharpest edge.

4.5.1 Bound atomic reference types are more safe than atomic_ref

Our 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);
}

4.5.2 Bound accessor types make array-based algorithms safer

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.

4.5.3 Atomic access itself is the sharpest edge

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.

5 Open questions

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.

6 Wording

The proposed changes are relative to N5054:

6.1 Bound atomic ref

6.1.1 Change the header synopsis for atomic in [atomics.syn]:

[ 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*>;

6.1.2 Add the following just before [atomics.types.generic]:

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:

  • (2.1) 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:

  • (6.1) T and U are similar types ([conv.qual]), and

  • (6.2) is_convertible_v<U*, T*> is true.

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:

  • (2.1) 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:

  • (6.1) T and U are similar types ([conv.qual]), and

  • (6.2) is_convertible_v<U*, T*> is true.

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>;
}

6.1.3 In [version.syn]:

Update the feature test macro __cpp_lib_atomic_ref.

6.2 Atomic Accessors

6.2.1 Add to the mdspan header synopsis in [mdspan.syn]

// [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;

6.2.2 Put the following before [mdspan.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>>;
}

6.3 In [version.syn]

Add the following feature test macro:

#define __cpp_lib_atomic_accessors YYYYMML // also in <mdspan>

7 Acknowledgments

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.