[libcxx-commits] [libcxx] [libc++] cv-qualified types in atomic and atomic_ref (P3323R1) (PR #121414)

Damien L-G via libcxx-commits libcxx-commits at lists.llvm.org
Tue Dec 31 13:10:04 PST 2024


https://github.com/dalg24 created https://github.com/llvm/llvm-project/pull/121414

Implement P3323R1 as a DR against C++11(?), respectively C++20, for `std::atomic` and `std::atomic_ref`.

The `std::atomic` changes are straightforward, adding a couple static assertions checking that the class template is only instantiated for cv-unqualified types, along with a test adapted from `libcxx/test/std/atomics/atomics.types.generic/trivially_copyable.verify.cpp`.

The `std::atomic_ref` changes are more involved.  I started converting a subset of the tests to add coverage for volatile- and const-qualified types but I would like feedback before I process the rest.

>From 5afe474ddd2eab652ac24cddacc003957578d3f6 Mon Sep 17 00:00:00 2001
From: Damien L-G <dalg24 at gmail.com>
Date: Tue, 31 Dec 2024 14:38:33 -0500
Subject: [PATCH 1/4] [libc++] cv-qualified types in atomic_ref

Implement atomic_ref related fixes from P3323 as a DR against C++20
---
 libcxx/include/__atomic/atomic_ref.h | 276 ++++++++++++++++++++-------
 1 file changed, 212 insertions(+), 64 deletions(-)

diff --git a/libcxx/include/__atomic/atomic_ref.h b/libcxx/include/__atomic/atomic_ref.h
index eef15983b98331..f7ed626fed75da 100644
--- a/libcxx/include/__atomic/atomic_ref.h
+++ b/libcxx/include/__atomic/atomic_ref.h
@@ -29,7 +29,12 @@
 #include <__cstddef/ptrdiff_t.h>
 #include <__memory/addressof.h>
 #include <__type_traits/has_unique_object_representation.h>
+#include <__type_traits/is_const.h>
+#include <__type_traits/is_pointer.h>
 #include <__type_traits/is_trivially_copyable.h>
+#include <__type_traits/is_volatile.h>
+#include <__type_traits/remove_cv.h>
+#include <__type_traits/remove_pointer.h>
 #include <cstdint>
 #include <cstring>
 
@@ -110,7 +115,7 @@ struct __atomic_ref_base {
   static constexpr size_t __min_alignment = (sizeof(_Tp) & (sizeof(_Tp) - 1)) || (sizeof(_Tp) > 16) ? 0 : sizeof(_Tp);
 
 public:
-  using value_type = _Tp;
+  using value_type = __remove_cv_t<_Tp>;
 
   static constexpr size_t required_alignment = alignof(_Tp) > __min_alignment ? alignof(_Tp) : __min_alignment;
 
@@ -123,43 +128,50 @@ struct __atomic_ref_base {
 
   _LIBCPP_HIDE_FROM_ABI bool is_lock_free() const noexcept { return __atomic_is_lock_free(sizeof(_Tp), __ptr_); }
 
-  _LIBCPP_HIDE_FROM_ABI void store(_Tp __desired, memory_order __order = memory_order::seq_cst) const noexcept
-      _LIBCPP_CHECK_STORE_MEMORY_ORDER(__order) {
+  _LIBCPP_HIDE_FROM_ABI void store(value_type __desired, memory_order __order = memory_order::seq_cst) const noexcept
+    requires(!is_const_v<_Tp>)
+  _LIBCPP_CHECK_STORE_MEMORY_ORDER(__order) {
     _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
         __order == memory_order::relaxed || __order == memory_order::release || __order == memory_order::seq_cst,
         "atomic_ref: memory order argument to atomic store operation is invalid");
     __atomic_store(__ptr_, __clear_padding(__desired), std::__to_gcc_order(__order));
   }
 
-  _LIBCPP_HIDE_FROM_ABI _Tp operator=(_Tp __desired) const noexcept {
+  _LIBCPP_HIDE_FROM_ABI value_type operator=(value_type __desired) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
     store(__desired);
     return __desired;
   }
 
-  _LIBCPP_HIDE_FROM_ABI _Tp load(memory_order __order = memory_order::seq_cst) const noexcept
+  _LIBCPP_HIDE_FROM_ABI value_type load(memory_order __order = memory_order::seq_cst) const noexcept
       _LIBCPP_CHECK_LOAD_MEMORY_ORDER(__order) {
     _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
         __order == memory_order::relaxed || __order == memory_order::consume || __order == memory_order::acquire ||
             __order == memory_order::seq_cst,
         "atomic_ref: memory order argument to atomic load operation is invalid");
-    alignas(_Tp) byte __mem[sizeof(_Tp)];
-    auto* __ret = reinterpret_cast<_Tp*>(__mem);
+    alignas(value_type) byte __mem[sizeof(value_type)];
+    auto* __ret = reinterpret_cast<value_type*>(__mem);
     __atomic_load(__ptr_, __ret, std::__to_gcc_order(__order));
     return *__ret;
   }
 
-  _LIBCPP_HIDE_FROM_ABI operator _Tp() const noexcept { return load(); }
+  _LIBCPP_HIDE_FROM_ABI operator value_type() const noexcept { return load(); }
 
-  _LIBCPP_HIDE_FROM_ABI _Tp exchange(_Tp __desired, memory_order __order = memory_order::seq_cst) const noexcept {
-    alignas(_Tp) byte __mem[sizeof(_Tp)];
-    auto* __ret = reinterpret_cast<_Tp*>(__mem);
+  _LIBCPP_HIDE_FROM_ABI value_type
+  exchange(value_type __desired, memory_order __order = memory_order::seq_cst) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    alignas(value_type) byte __mem[sizeof(value_type)];
+    auto* __ret = reinterpret_cast<value_type*>(__mem);
     __atomic_exchange(__ptr_, __clear_padding(__desired), __ret, std::__to_gcc_order(__order));
     return *__ret;
   }
 
-  _LIBCPP_HIDE_FROM_ABI bool
-  compare_exchange_weak(_Tp& __expected, _Tp __desired, memory_order __success, memory_order __failure) const noexcept
-      _LIBCPP_CHECK_EXCHANGE_MEMORY_ORDER(__success, __failure) {
+  _LIBCPP_HIDE_FROM_ABI bool compare_exchange_weak(
+      value_type& __expected, value_type __desired, memory_order __success, memory_order __failure) const noexcept
+    requires(!is_const_v<_Tp>)
+  _LIBCPP_CHECK_EXCHANGE_MEMORY_ORDER(__success, __failure) {
     _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
         __failure == memory_order::relaxed || __failure == memory_order::consume ||
             __failure == memory_order::acquire || __failure == memory_order::seq_cst,
@@ -172,9 +184,10 @@ struct __atomic_ref_base {
         std::__to_gcc_order(__success),
         std::__to_gcc_order(__failure));
   }
-  _LIBCPP_HIDE_FROM_ABI bool
-  compare_exchange_strong(_Tp& __expected, _Tp __desired, memory_order __success, memory_order __failure) const noexcept
-      _LIBCPP_CHECK_EXCHANGE_MEMORY_ORDER(__success, __failure) {
+  _LIBCPP_HIDE_FROM_ABI bool compare_exchange_strong(
+      value_type& __expected, value_type __desired, memory_order __success, memory_order __failure) const noexcept
+    requires(!is_const_v<_Tp>)
+  _LIBCPP_CHECK_EXCHANGE_MEMORY_ORDER(__success, __failure) {
     _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
         __failure == memory_order::relaxed || __failure == memory_order::consume ||
             __failure == memory_order::acquire || __failure == memory_order::seq_cst,
@@ -188,8 +201,10 @@ struct __atomic_ref_base {
         std::__to_gcc_order(__failure));
   }
 
-  _LIBCPP_HIDE_FROM_ABI bool
-  compare_exchange_weak(_Tp& __expected, _Tp __desired, memory_order __order = memory_order::seq_cst) const noexcept {
+  _LIBCPP_HIDE_FROM_ABI bool compare_exchange_weak(
+      value_type& __expected, value_type __desired, memory_order __order = memory_order::seq_cst) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
     return __compare_exchange(
         __ptr_,
         std::addressof(__expected),
@@ -198,8 +213,10 @@ struct __atomic_ref_base {
         std::__to_gcc_order(__order),
         std::__to_gcc_failure_order(__order));
   }
-  _LIBCPP_HIDE_FROM_ABI bool
-  compare_exchange_strong(_Tp& __expected, _Tp __desired, memory_order __order = memory_order::seq_cst) const noexcept {
+  _LIBCPP_HIDE_FROM_ABI bool compare_exchange_strong(
+      value_type& __expected, value_type __desired, memory_order __order = memory_order::seq_cst) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
     return __compare_exchange(
         __ptr_,
         std::addressof(__expected),
@@ -209,7 +226,7 @@ struct __atomic_ref_base {
         std::__to_gcc_failure_order(__order));
   }
 
-  _LIBCPP_HIDE_FROM_ABI void wait(_Tp __old, memory_order __order = memory_order::seq_cst) const noexcept
+  _LIBCPP_HIDE_FROM_ABI void wait(value_type __old, memory_order __order = memory_order::seq_cst) const noexcept
       _LIBCPP_CHECK_WAIT_MEMORY_ORDER(__order) {
     _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
         __order == memory_order::relaxed || __order == memory_order::consume || __order == memory_order::acquire ||
@@ -217,8 +234,16 @@ struct __atomic_ref_base {
         "atomic_ref: memory order argument to atomic wait operation is invalid");
     std::__atomic_wait(*this, __old, __order);
   }
-  _LIBCPP_HIDE_FROM_ABI void notify_one() const noexcept { std::__atomic_notify_one(*this); }
-  _LIBCPP_HIDE_FROM_ABI void notify_all() const noexcept { std::__atomic_notify_all(*this); }
+  _LIBCPP_HIDE_FROM_ABI void notify_one() const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    std::__atomic_notify_one(*this);
+  }
+  _LIBCPP_HIDE_FROM_ABI void notify_all() const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    std::__atomic_notify_all(*this);
+  }
 
 protected:
   using _Aligned_Tp [[__gnu__::__aligned__(required_alignment)]] = _Tp;
@@ -243,6 +268,8 @@ struct atomic_ref : public __atomic_ref_base<_Tp> {
 
   using __base = __atomic_ref_base<_Tp>;
 
+  static_assert(__base::is_always_lock_free || !is_volatile_v<_Tp>);
+
   _LIBCPP_HIDE_FROM_ABI explicit atomic_ref(_Tp& __obj) : __base(__obj) {
     _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
         reinterpret_cast<uintptr_t>(std::addressof(__obj)) % __base::required_alignment == 0,
@@ -251,17 +278,24 @@ struct atomic_ref : public __atomic_ref_base<_Tp> {
 
   _LIBCPP_HIDE_FROM_ABI atomic_ref(const atomic_ref&) noexcept = default;
 
-  _LIBCPP_HIDE_FROM_ABI _Tp operator=(_Tp __desired) const noexcept { return __base::operator=(__desired); }
+  _LIBCPP_HIDE_FROM_ABI __base::value_type operator=(__base::value_type __desired) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return __base::operator=(__desired);
+  }
 
   atomic_ref& operator=(const atomic_ref&) = delete;
 };
 
 template <class _Tp>
-  requires(std::integral<_Tp> && !std::same_as<bool, _Tp>)
+  requires(std::integral<_Tp> && !std::same_as<bool, __remove_cv_t<_Tp>>)
 struct atomic_ref<_Tp> : public __atomic_ref_base<_Tp> {
   using __base = __atomic_ref_base<_Tp>;
 
+  static_assert(__base::is_always_lock_free || !is_volatile_v<_Tp>);
+
   using difference_type = __base::value_type;
+  using value_type      = __base::value_type;
 
   _LIBCPP_HIDE_FROM_ABI explicit atomic_ref(_Tp& __obj) : __base(__obj) {
     _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
@@ -271,35 +305,90 @@ struct atomic_ref<_Tp> : public __atomic_ref_base<_Tp> {
 
   _LIBCPP_HIDE_FROM_ABI atomic_ref(const atomic_ref&) noexcept = default;
 
-  _LIBCPP_HIDE_FROM_ABI _Tp operator=(_Tp __desired) const noexcept { return __base::operator=(__desired); }
+  _LIBCPP_HIDE_FROM_ABI value_type operator=(value_type __desired) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return __base::operator=(__desired);
+  }
 
   atomic_ref& operator=(const atomic_ref&) = delete;
 
-  _LIBCPP_HIDE_FROM_ABI _Tp fetch_add(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
+  _LIBCPP_HIDE_FROM_ABI value_type
+  fetch_add(value_type __arg, memory_order __order = memory_order_seq_cst) const noexcept
+    requires(!is_const_v<value_type>)
+  {
     return __atomic_fetch_add(this->__ptr_, __arg, std::__to_gcc_order(__order));
   }
-  _LIBCPP_HIDE_FROM_ABI _Tp fetch_sub(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
+  _LIBCPP_HIDE_FROM_ABI value_type
+  fetch_sub(value_type __arg, memory_order __order = memory_order_seq_cst) const noexcept
+    requires(!is_const_v<value_type>)
+  {
     return __atomic_fetch_sub(this->__ptr_, __arg, std::__to_gcc_order(__order));
   }
-  _LIBCPP_HIDE_FROM_ABI _Tp fetch_and(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
+  _LIBCPP_HIDE_FROM_ABI value_type
+  fetch_and(value_type __arg, memory_order __order = memory_order_seq_cst) const noexcept
+    requires(!is_const_v<value_type>)
+  {
     return __atomic_fetch_and(this->__ptr_, __arg, std::__to_gcc_order(__order));
   }
-  _LIBCPP_HIDE_FROM_ABI _Tp fetch_or(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
+  _LIBCPP_HIDE_FROM_ABI value_type
+  fetch_or(value_type __arg, memory_order __order = memory_order_seq_cst) const noexcept
+    requires(!is_const_v<value_type>)
+  {
     return __atomic_fetch_or(this->__ptr_, __arg, std::__to_gcc_order(__order));
   }
-  _LIBCPP_HIDE_FROM_ABI _Tp fetch_xor(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
+  _LIBCPP_HIDE_FROM_ABI value_type
+  fetch_xor(value_type __arg, memory_order __order = memory_order_seq_cst) const noexcept
+    requires(!is_const_v<value_type>)
+  {
     return __atomic_fetch_xor(this->__ptr_, __arg, std::__to_gcc_order(__order));
   }
 
-  _LIBCPP_HIDE_FROM_ABI _Tp operator++(int) const noexcept { return fetch_add(_Tp(1)); }
-  _LIBCPP_HIDE_FROM_ABI _Tp operator--(int) const noexcept { return fetch_sub(_Tp(1)); }
-  _LIBCPP_HIDE_FROM_ABI _Tp operator++() const noexcept { return fetch_add(_Tp(1)) + _Tp(1); }
-  _LIBCPP_HIDE_FROM_ABI _Tp operator--() const noexcept { return fetch_sub(_Tp(1)) - _Tp(1); }
-  _LIBCPP_HIDE_FROM_ABI _Tp operator+=(_Tp __arg) const noexcept { return fetch_add(__arg) + __arg; }
-  _LIBCPP_HIDE_FROM_ABI _Tp operator-=(_Tp __arg) const noexcept { return fetch_sub(__arg) - __arg; }
-  _LIBCPP_HIDE_FROM_ABI _Tp operator&=(_Tp __arg) const noexcept { return fetch_and(__arg) & __arg; }
-  _LIBCPP_HIDE_FROM_ABI _Tp operator|=(_Tp __arg) const noexcept { return fetch_or(__arg) | __arg; }
-  _LIBCPP_HIDE_FROM_ABI _Tp operator^=(_Tp __arg) const noexcept { return fetch_xor(__arg) ^ __arg; }
+  _LIBCPP_HIDE_FROM_ABI value_type operator++(int) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_add(value_type(1));
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator--(int) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_sub(value_type(1));
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator++() const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_add(value_type(1)) + value_type(1);
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator--() const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_sub(value_type(1)) - value_type(1);
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator+=(value_type __arg) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_add(__arg) + __arg;
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator-=(value_type __arg) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_sub(__arg) - __arg;
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator&=(value_type __arg) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_and(__arg) & __arg;
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator|=(value_type __arg) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_or(__arg) | __arg;
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator^=(value_type __arg) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_xor(__arg) ^ __arg;
+  }
 };
 
 template <class _Tp>
@@ -307,7 +396,10 @@ template <class _Tp>
 struct atomic_ref<_Tp> : public __atomic_ref_base<_Tp> {
   using __base = __atomic_ref_base<_Tp>;
 
+  static_assert(__base::is_always_lock_free || !is_volatile_v<_Tp>);
+
   using difference_type = __base::value_type;
+  using value_type      = __base::value_type;
 
   _LIBCPP_HIDE_FROM_ABI explicit atomic_ref(_Tp& __obj) : __base(__obj) {
     _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
@@ -317,56 +409,112 @@ struct atomic_ref<_Tp> : public __atomic_ref_base<_Tp> {
 
   _LIBCPP_HIDE_FROM_ABI atomic_ref(const atomic_ref&) noexcept = default;
 
-  _LIBCPP_HIDE_FROM_ABI _Tp operator=(_Tp __desired) const noexcept { return __base::operator=(__desired); }
+  _LIBCPP_HIDE_FROM_ABI value_type operator=(value_type __desired) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return __base::operator=(__desired);
+  }
 
   atomic_ref& operator=(const atomic_ref&) = delete;
 
-  _LIBCPP_HIDE_FROM_ABI _Tp fetch_add(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
-    _Tp __old = this->load(memory_order_relaxed);
-    _Tp __new = __old + __arg;
+  _LIBCPP_HIDE_FROM_ABI value_type
+  fetch_add(value_type __arg, memory_order __order = memory_order_seq_cst) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    value_type __old = this->load(memory_order_relaxed);
+    value_type __new = __old + __arg;
     while (!this->compare_exchange_weak(__old, __new, __order, memory_order_relaxed)) {
       __new = __old + __arg;
     }
     return __old;
   }
-  _LIBCPP_HIDE_FROM_ABI _Tp fetch_sub(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
-    _Tp __old = this->load(memory_order_relaxed);
-    _Tp __new = __old - __arg;
+  _LIBCPP_HIDE_FROM_ABI value_type
+  fetch_sub(value_type __arg, memory_order __order = memory_order_seq_cst) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    value_type __old = this->load(memory_order_relaxed);
+    value_type __new = __old - __arg;
     while (!this->compare_exchange_weak(__old, __new, __order, memory_order_relaxed)) {
       __new = __old - __arg;
     }
     return __old;
   }
 
-  _LIBCPP_HIDE_FROM_ABI _Tp operator+=(_Tp __arg) const noexcept { return fetch_add(__arg) + __arg; }
-  _LIBCPP_HIDE_FROM_ABI _Tp operator-=(_Tp __arg) const noexcept { return fetch_sub(__arg) - __arg; }
+  _LIBCPP_HIDE_FROM_ABI value_type operator+=(value_type __arg) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_add(__arg) + __arg;
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator-=(value_type __arg) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_sub(__arg) - __arg;
+  }
 };
 
 template <class _Tp>
-struct atomic_ref<_Tp*> : public __atomic_ref_base<_Tp*> {
-  using __base = __atomic_ref_base<_Tp*>;
+  requires(std::is_pointer_v<_Tp>)
+struct atomic_ref<_Tp> : public __atomic_ref_base<_Tp> {
+  using __base = __atomic_ref_base<_Tp>;
 
   using difference_type = ptrdiff_t;
+  using value_type      = typename __base::value_type;
 
-  _LIBCPP_HIDE_FROM_ABI explicit atomic_ref(_Tp*& __ptr) : __base(__ptr) {}
+  _LIBCPP_HIDE_FROM_ABI explicit atomic_ref(_Tp& __ptr) : __base(__ptr) {}
 
-  _LIBCPP_HIDE_FROM_ABI _Tp* operator=(_Tp* __desired) const noexcept { return __base::operator=(__desired); }
+  _LIBCPP_HIDE_FROM_ABI value_type operator=(value_type __desired) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return __base::operator=(__desired);
+  }
 
   atomic_ref& operator=(const atomic_ref&) = delete;
 
-  _LIBCPP_HIDE_FROM_ABI _Tp* fetch_add(ptrdiff_t __arg, memory_order __order = memory_order_seq_cst) const noexcept {
-    return __atomic_fetch_add(this->__ptr_, __arg * sizeof(_Tp), std::__to_gcc_order(__order));
+  _LIBCPP_HIDE_FROM_ABI value_type
+  fetch_add(ptrdiff_t __arg, memory_order __order = memory_order_seq_cst) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return __atomic_fetch_add(
+        this->__ptr_, __arg * sizeof(__remove_pointer_t<value_type>), std::__to_gcc_order(__order));
   }
-  _LIBCPP_HIDE_FROM_ABI _Tp* fetch_sub(ptrdiff_t __arg, memory_order __order = memory_order_seq_cst) const noexcept {
-    return __atomic_fetch_sub(this->__ptr_, __arg * sizeof(_Tp), std::__to_gcc_order(__order));
+  _LIBCPP_HIDE_FROM_ABI value_type
+  fetch_sub(ptrdiff_t __arg, memory_order __order = memory_order_seq_cst) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return __atomic_fetch_sub(
+        this->__ptr_, __arg * sizeof(__remove_pointer_t<value_type>), std::__to_gcc_order(__order));
   }
 
-  _LIBCPP_HIDE_FROM_ABI _Tp* operator++(int) const noexcept { return fetch_add(1); }
-  _LIBCPP_HIDE_FROM_ABI _Tp* operator--(int) const noexcept { return fetch_sub(1); }
-  _LIBCPP_HIDE_FROM_ABI _Tp* operator++() const noexcept { return fetch_add(1) + 1; }
-  _LIBCPP_HIDE_FROM_ABI _Tp* operator--() const noexcept { return fetch_sub(1) - 1; }
-  _LIBCPP_HIDE_FROM_ABI _Tp* operator+=(ptrdiff_t __arg) const noexcept { return fetch_add(__arg) + __arg; }
-  _LIBCPP_HIDE_FROM_ABI _Tp* operator-=(ptrdiff_t __arg) const noexcept { return fetch_sub(__arg) - __arg; }
+  _LIBCPP_HIDE_FROM_ABI value_type operator++(int) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_add(1);
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator--(int) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_sub(1);
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator++() const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_add(1) + 1;
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator--() const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_sub(1) - 1;
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator+=(ptrdiff_t __arg) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_add(__arg) + __arg;
+  }
+  _LIBCPP_HIDE_FROM_ABI value_type operator-=(ptrdiff_t __arg) const noexcept
+    requires(!is_const_v<_Tp>)
+  {
+    return fetch_sub(__arg) - __arg;
+  }
 };
 
 _LIBCPP_CTAD_SUPPORTED_FOR_TYPE(atomic_ref);

>From fc886c3e9ac658474430a64c324cd7c14eb881ae Mon Sep 17 00:00:00 2001
From: Damien L-G <dalg24 at gmail.com>
Date: Tue, 31 Dec 2024 15:46:35 -0500
Subject: [PATCH 2/4] [libc++] check that std::atomic is only instantiated for
 cv-unqualified types

---
 libcxx/include/__atomic/support.h             |  4 +++
 .../__cxx03/__atomic/cxx_atomic_impl.h        |  4 +++
 .../cv_unqualified.verify.cpp                 | 25 +++++++++++++++++++
 3 files changed, 33 insertions(+)
 create mode 100644 libcxx/test/std/atomics/atomics.types.generic/cv_unqualified.verify.cpp

diff --git a/libcxx/include/__atomic/support.h b/libcxx/include/__atomic/support.h
index 4b555ab483ca0e..e0e1d3b008a393 100644
--- a/libcxx/include/__atomic/support.h
+++ b/libcxx/include/__atomic/support.h
@@ -10,7 +10,9 @@
 #define _LIBCPP___ATOMIC_SUPPORT_H
 
 #include <__config>
+#include <__type_traits/is_same.h>
 #include <__type_traits/is_trivially_copyable.h>
+#include <__type_traits/remove_cv.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -114,6 +116,8 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 template <typename _Tp, typename _Base = __cxx_atomic_base_impl<_Tp> >
 struct __cxx_atomic_impl : public _Base {
   static_assert(is_trivially_copyable<_Tp>::value, "std::atomic<T> requires that 'T' be a trivially copyable type");
+  static_assert(is_same<_Tp, typename remove_cv<_Tp>::type>::value,
+                "std::atomic<T> requires that 'T' be a cv-unqualified type");
 
   _LIBCPP_HIDE_FROM_ABI __cxx_atomic_impl() _NOEXCEPT = default;
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR explicit __cxx_atomic_impl(_Tp __value) _NOEXCEPT : _Base(__value) {}
diff --git a/libcxx/include/__cxx03/__atomic/cxx_atomic_impl.h b/libcxx/include/__cxx03/__atomic/cxx_atomic_impl.h
index 990d283c62d1a5..7d428591e33758 100644
--- a/libcxx/include/__cxx03/__atomic/cxx_atomic_impl.h
+++ b/libcxx/include/__cxx03/__atomic/cxx_atomic_impl.h
@@ -14,8 +14,10 @@
 #include <__cxx03/__config>
 #include <__cxx03/__memory/addressof.h>
 #include <__cxx03/__type_traits/is_assignable.h>
+#include <__cxx03/__type_traits/is_same.h>
 #include <__cxx03/__type_traits/is_trivially_copyable.h>
 #include <__cxx03/__type_traits/remove_const.h>
+#include <__cxx03/__type_traits/remove_cv.h>
 #include <__cxx03/cstddef>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -500,6 +502,8 @@ __cxx_atomic_fetch_xor(__cxx_atomic_base_impl<_Tp>* __a, _Tp __pattern, memory_o
 template <typename _Tp, typename _Base = __cxx_atomic_base_impl<_Tp> >
 struct __cxx_atomic_impl : public _Base {
   static_assert(is_trivially_copyable<_Tp>::value, "std::atomic<T> requires that 'T' be a trivially copyable type");
+  static_assert(is_same<_Tp, typename remove_cv<_Tp>::type>::value,
+                "std::atomic<T> requires that 'T' be a cv-unqualified type");
 
   _LIBCPP_HIDE_FROM_ABI __cxx_atomic_impl() _NOEXCEPT = default;
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR explicit __cxx_atomic_impl(_Tp __value) _NOEXCEPT : _Base(__value) {}
diff --git a/libcxx/test/std/atomics/atomics.types.generic/cv_unqualified.verify.cpp b/libcxx/test/std/atomics/atomics.types.generic/cv_unqualified.verify.cpp
new file mode 100644
index 00000000000000..c13c246cfd4eed
--- /dev/null
+++ b/libcxx/test/std/atomics/atomics.types.generic/cv_unqualified.verify.cpp
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <atomic>
+
+// template <class T>
+// struct atomic;
+
+// This test checks that we static_assert inside std::atomic<T> when T
+// is cv-qualified, however Clang will sometimes emit additional
+// errors while trying to instantiate the rest of std::atomic<T>.
+// We silence those to make the test more robust.
+// ADDITIONAL_COMPILE_FLAGS: -Xclang -verify-ignore-unexpected=error
+
+#include <atomic>
+
+void f() {
+  std::atomic<const int> a; // expected-error@*:* {{std::atomic<T> requires that 'T' be a cv-unqualified type}}
+}
+

>From 69d92fa1adcaed6e321fee1920bdad85e98c30ce Mon Sep 17 00:00:00 2001
From: Damien L-G <dalg24 at gmail.com>
Date: Tue, 31 Dec 2024 15:50:07 -0500
Subject: [PATCH 3/4] [libc++] Mark P3323R1 as complete

---
 libcxx/docs/Status/Cxx2cPapers.csv | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index 3a8a794ca4ea1e..990c65a4993a3a 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -87,7 +87,7 @@
 "`P3050R2 <https://wg21.link/P3050R2>`__","Fix C++26 by optimizing ``linalg::conjugated`` for noncomplex value types","2024-11 (Wrocław)","","",""
 "`P3396R1 <https://wg21.link/P3396R1>`__","``std::execution`` wording fixes","2024-11 (Wrocław)","","",""
 "`P2835R7 <https://wg21.link/P2835R7>`__","Expose ``std::atomic_ref``'s object address","2024-11 (Wrocław)","","",""
-"`P3323R1 <https://wg21.link/P3323R1>`__","cv-qualified types in ``atomic`` and ``atomic_ref``","2024-11 (Wrocław)","","",""
+"`P3323R1 <https://wg21.link/P3323R1>`__","cv-qualified types in ``atomic`` and ``atomic_ref``","2024-11 (Wrocław)","|Complete|","20","Implemented as DR against C++20."
 "`P3508R0 <https://wg21.link/P3508R0>`__","Wording for ""constexpr for specialized memory algorithms""","2024-11 (Wrocław)","","",""
 "`P3369R0 <https://wg21.link/P3369R0>`__","constexpr for ``uninitialized_default_construct``","2024-11 (Wrocław)","","",""
 "`P3370R1 <https://wg21.link/P3370R1>`__","Add new library headers from C23","2024-11 (Wrocław)","","",""

>From 9c6dd660ebeb1014ba1fe6eefcd32c08cbe9f08b Mon Sep 17 00:00:00 2001
From: Damien L-G <dalg24 at gmail.com>
Date: Tue, 31 Dec 2024 15:54:48 -0500
Subject: [PATCH 4/4] [libc++] Add testing for atomic_ref store/load for
 cv-qualified types

---
 .../std/atomics/atomics.ref/assign.pass.cpp   | 42 +++++++++++++------
 .../std/atomics/atomics.ref/convert.pass.cpp  | 34 +++++++++++----
 .../atomics/atomics.ref/deduction.pass.cpp    | 13 +++++-
 .../std/atomics/atomics.ref/load.pass.cpp     | 35 ++++++++++++----
 .../std/atomics/atomics.ref/store.pass.cpp    | 40 ++++++++++++++----
 .../std/atomics/atomics.ref/test_helper.h     | 39 ++++++++---------
 6 files changed, 146 insertions(+), 57 deletions(-)

diff --git a/libcxx/test/std/atomics/atomics.ref/assign.pass.cpp b/libcxx/test/std/atomics/atomics.ref/assign.pass.cpp
index 9b2f9042e9836f..f9dcb3c37e6ab7 100644
--- a/libcxx/test/std/atomics/atomics.ref/assign.pass.cpp
+++ b/libcxx/test/std/atomics/atomics.ref/assign.pass.cpp
@@ -20,26 +20,42 @@
 #include "test_helper.h"
 #include "test_macros.h"
 
-template <typename T>
+template <typename U>
 struct TestAssign {
   void operator()() const {
-    {
-      T x(T(1));
-      std::atomic_ref<T> const a(x);
+    static_assert(std::is_nothrow_assignable_v<std::atomic_ref<U>, U>);
+    do_test<U>();
+    do_test_atomic<U>();
+    static_assert(!std::is_assignable_v<std::atomic_ref<U const>, U>);
+    if constexpr (std::atomic_ref<U>::is_always_lock_free) {
+      static_assert(std::is_nothrow_assignable_v<std::atomic_ref<U volatile>, U>);
+      do_test<U volatile>();
+      do_test_atomic<U volatile>();
+      static_assert(!std::is_assignable_v<std::atomic_ref<U const volatile>, U>);
+    }
+  }
 
-      std::same_as<T> decltype(auto) y = (a = T(2));
-      assert(y == T(2));
-      assert(x == T(2));
+  template <typename T>
+  void do_test() const {
+    T x(T(1));
+    std::atomic_ref<T> const a(x);
 
-      ASSERT_NOEXCEPT(a = T(0));
-      static_assert(std::is_nothrow_assignable_v<std::atomic_ref<T>, T>);
+    std::same_as<std::remove_cv_t<T>> decltype(auto) y = (a = T(2));
+    assert(y == std::remove_cv_t<T>(2));
+    assert(const_cast<std::remove_cv_t<T> const&>(x) == std::remove_cv_t<T>(2));
 
-      static_assert(!std::is_copy_assignable_v<std::atomic_ref<T>>);
-    }
+    ASSERT_NOEXCEPT(a = T(0));
+
+    static_assert(!std::is_copy_assignable_v<std::atomic_ref<T>>);
+  }
 
+  template <typename T>
+  void do_test_atomic() const {
     {
-      auto assign = [](std::atomic_ref<T> const& y, T, T new_val) { y = new_val; };
-      auto load   = [](std::atomic_ref<T> const& y) { return y.load(); };
+      auto assign = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
+        y = const_cast<std::remove_cv_t<T> const&>(new_val);
+      };
+      auto load = [](std::atomic_ref<T> const& y) { return y.load(); };
       test_seq_cst<T>(assign, load);
     }
   }
diff --git a/libcxx/test/std/atomics/atomics.ref/convert.pass.cpp b/libcxx/test/std/atomics/atomics.ref/convert.pass.cpp
index 2a58a5ea6ae2ab..754065753331d1 100644
--- a/libcxx/test/std/atomics/atomics.ref/convert.pass.cpp
+++ b/libcxx/test/std/atomics/atomics.ref/convert.pass.cpp
@@ -19,22 +19,42 @@
 #include "test_helper.h"
 #include "test_macros.h"
 
-template <typename T>
+template <typename U>
 struct TestConvert {
   void operator()() const {
+    static_assert(std::is_nothrow_convertible_v<std::atomic_ref<U>, U>);
+    do_test<U>();
+    do_test_atomic<U>();
+    static_assert(std::is_nothrow_convertible_v<std::atomic_ref<U const>, U>);
+    do_test<U const>();
+    if constexpr (std::atomic_ref<U>::is_always_lock_free) {
+      static_assert(std::is_nothrow_convertible_v<std::atomic_ref<U volatile>, U>);
+      do_test<U volatile>();
+      do_test_atomic<U volatile>();
+      static_assert(std::is_nothrow_convertible_v<std::atomic_ref<U const volatile>, U>);
+      do_test<U const volatile>();
+    }
+  }
+
+  template <class T>
+  void do_test() const {
     T x(T(1));
 
-    T copy = x;
+    T copy = const_cast<std::remove_cv_t<T> const&>(x);
     std::atomic_ref<T> const a(copy);
 
-    T converted = a;
-    assert(converted == x);
+    std::remove_cv_t<T> converted = a;
+    assert(converted == const_cast<std::remove_cv_t<T> const&>(x));
 
     ASSERT_NOEXCEPT(T(a));
-    static_assert(std::is_nothrow_convertible_v<std::atomic_ref<T>, T>);
+  }
 
-    auto store = [](std::atomic_ref<T> const& y, T, T new_val) { y.store(new_val); };
-    auto load  = [](std::atomic_ref<T> const& y) { return static_cast<T>(y); };
+  template <class T>
+  void do_test_atomic() const {
+    auto store = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
+      y.store(const_cast<std::remove_cv_t<T> const&>(new_val));
+    };
+    auto load = [](std::atomic_ref<T> const& y) { return static_cast<std::remove_cv_t<T>>(y); };
     test_seq_cst<T>(store, load);
   }
 };
diff --git a/libcxx/test/std/atomics/atomics.ref/deduction.pass.cpp b/libcxx/test/std/atomics/atomics.ref/deduction.pass.cpp
index 24a399ac4711e1..397ac88d0bf9c6 100644
--- a/libcxx/test/std/atomics/atomics.ref/deduction.pass.cpp
+++ b/libcxx/test/std/atomics/atomics.ref/deduction.pass.cpp
@@ -21,9 +21,18 @@
 template <typename T>
 struct TestDeduction {
   void operator()() const {
-    T x(T(0));
+    do_test<T>();
+    do_test<T const>();
+    if constexpr (std::atomic_ref<T>::is_always_lock_free) {
+      do_test<T volatile>();
+      do_test<T const volatile>();
+    }
+  }
+  template <class U>
+  void do_test() const {
+    U x(U(0));
     std::atomic_ref a(x);
-    ASSERT_SAME_TYPE(decltype(a), std::atomic_ref<T>);
+    ASSERT_SAME_TYPE(decltype(a), std::atomic_ref<U>);
   }
 };
 
diff --git a/libcxx/test/std/atomics/atomics.ref/load.pass.cpp b/libcxx/test/std/atomics/atomics.ref/load.pass.cpp
index feed0fbaed8428..ae702e3d5fc666 100644
--- a/libcxx/test/std/atomics/atomics.ref/load.pass.cpp
+++ b/libcxx/test/std/atomics/atomics.ref/load.pass.cpp
@@ -20,27 +20,44 @@
 #include "test_helper.h"
 #include "test_macros.h"
 
-template <typename T>
+template <typename U>
 struct TestLoad {
   void operator()() const {
+    do_test<U>();
+    do_test_atomic<U>();
+    do_test<U const>();
+    if constexpr (std::atomic_ref<U>::is_always_lock_free) {
+      do_test<U volatile>();
+      do_test_atomic<U volatile>();
+      do_test<U const volatile>();
+    }
+  }
+
+  template <class T>
+  void do_test() const {
     T x(T(1));
     std::atomic_ref<T> const a(x);
 
     {
-      std::same_as<T> decltype(auto) y = a.load();
-      assert(y == T(1));
+      std::same_as<std::remove_cv_t<T>> decltype(auto) y = a.load();
+      assert(y == std::remove_cv_t<T>(1));
       ASSERT_NOEXCEPT(a.load());
     }
 
     {
-      std::same_as<T> decltype(auto) y = a.load(std::memory_order_seq_cst);
-      assert(y == T(1));
+      std::same_as<std::remove_cv_t<T>> decltype(auto) y = a.load(std::memory_order_seq_cst);
+      assert(y == std::remove_cv_t<T>(1));
       ASSERT_NOEXCEPT(a.load(std::memory_order_seq_cst));
     }
+  }
 
+  template <class T>
+  void do_test_atomic() const {
     // memory_order::seq_cst
     {
-      auto store           = [](std::atomic_ref<T> const& y, T, T new_val) { y.store(new_val); };
+      auto store = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
+        y.store(const_cast<std::remove_cv_t<T> const&>(new_val));
+      };
       auto load_no_arg     = [](std::atomic_ref<T> const& y) { return y.load(); };
       auto load_with_order = [](std::atomic_ref<T> const& y) { return y.load(std::memory_order::seq_cst); };
       test_seq_cst<T>(store, load_no_arg);
@@ -49,8 +66,10 @@ struct TestLoad {
 
     // memory_order::release
     {
-      auto store = [](std::atomic_ref<T> const& y, T, T new_val) { y.store(new_val, std::memory_order::release); };
-      auto load  = [](std::atomic_ref<T> const& y) { return y.load(std::memory_order::acquire); };
+      auto store = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
+        y.store(const_cast<std::remove_cv_t<T> const&>(new_val), std::memory_order::release);
+      };
+      auto load = [](std::atomic_ref<T> const& y) { return y.load(std::memory_order::acquire); };
       test_acquire_release<T>(store, load);
     }
   }
diff --git a/libcxx/test/std/atomics/atomics.ref/store.pass.cpp b/libcxx/test/std/atomics/atomics.ref/store.pass.cpp
index ea01a3d02a34f9..7b9a4036878929 100644
--- a/libcxx/test/std/atomics/atomics.ref/store.pass.cpp
+++ b/libcxx/test/std/atomics/atomics.ref/store.pass.cpp
@@ -19,27 +19,49 @@
 #include "test_helper.h"
 #include "test_macros.h"
 
-template <typename T>
+template <typename T, typename U>
+concept has_store = requires { std::declval<T>().store(std::declval<U>()); };
+
+template <typename U>
 struct TestStore {
   void operator()() const {
+    static_assert(has_store<std::atomic_ref<U>, U>);
+    do_test<U>();
+    do_test_atomic<U>();
+    static_assert(!has_store<std::atomic_ref<U const>, U>);
+    if constexpr (std::atomic_ref<U>::is_always_lock_free) {
+      static_assert(has_store<std::atomic_ref<U volatile>, U>);
+      do_test<U volatile>();
+      do_test_atomic<U volatile>();
+      static_assert(!has_store<std::atomic_ref<U const volatile>, U>);
+    }
+  }
+
+  template <typename T>
+  void do_test() const {
     T x(T(1));
     std::atomic_ref<T> const a(x);
 
     a.store(T(2));
-    assert(x == T(2));
+    assert(const_cast<std::remove_cv_t<T> const&>(x) == std::remove_cv_t<T>(2));
     ASSERT_NOEXCEPT(a.store(T(1)));
 
     a.store(T(3), std::memory_order_seq_cst);
-    assert(x == T(3));
+    assert(const_cast<std::remove_cv_t<T> const&>(x) == std::remove_cv_t<T>(3));
     ASSERT_NOEXCEPT(a.store(T(0), std::memory_order_seq_cst));
+  }
 
+  template <typename T>
+  void do_test_atomic() const {
     // TODO memory_order::relaxed
 
     // memory_order::seq_cst
     {
-      auto store_no_arg     = [](std::atomic_ref<T> const& y, T, T new_val) { y.store(new_val); };
-      auto store_with_order = [](std::atomic_ref<T> const& y, T, T new_val) {
-        y.store(new_val, std::memory_order::seq_cst);
+      auto store_no_arg = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
+        y.store(const_cast<std::remove_cv_t<T> const&>(new_val));
+      };
+      auto store_with_order = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
+        y.store(const_cast<std::remove_cv_t<T> const&>(new_val), std::memory_order::seq_cst);
       };
       auto load = [](std::atomic_ref<T> const& y) { return y.load(); };
       test_seq_cst<T>(store_no_arg, load);
@@ -48,8 +70,10 @@ struct TestStore {
 
     // memory_order::release
     {
-      auto store = [](std::atomic_ref<T> const& y, T, T new_val) { y.store(new_val, std::memory_order::release); };
-      auto load  = [](std::atomic_ref<T> const& y) { return y.load(std::memory_order::acquire); };
+      auto store = [](std::atomic_ref<T> const& y, T const&, T const& new_val) {
+        y.store(const_cast<std::remove_cv_t<T> const&>(new_val), std::memory_order::release);
+      };
+      auto load = [](std::atomic_ref<T> const& y) { return y.load(std::memory_order::acquire); };
       test_acquire_release<T>(store, load);
     }
   }
diff --git a/libcxx/test/std/atomics/atomics.ref/test_helper.h b/libcxx/test/std/atomics/atomics.ref/test_helper.h
index 225a70c5a16ca8..4f6c6b16b3dd1f 100644
--- a/libcxx/test/std/atomics/atomics.ref/test_helper.h
+++ b/libcxx/test/std/atomics/atomics.ref/test_helper.h
@@ -45,12 +45,12 @@ template <class T, class StoreOp, class LoadOp>
 void test_seq_cst(StoreOp store_op, LoadOp load_op) {
 #ifndef TEST_HAS_NO_THREADS
   for (int i = 0; i < 100; ++i) {
-    T old_value(make_value<T>(0));
-    T new_value(make_value<T>(1));
+    T old_value(make_value<std::remove_cv_t<T>>(0));
+    T new_value(make_value<std::remove_cv_t<T>>(1));
 
-    T copy_x = old_value;
+    T copy_x = const_cast<std::remove_cv_t<T> const&>(old_value);
     std::atomic_ref<T> const x(copy_x);
-    T copy_y = old_value;
+    T copy_y = const_cast<std::remove_cv_t<T> const&>(old_value);
     std::atomic_ref<T> const y(copy_y);
 
     std::atomic_bool x_updated_first(false);
@@ -61,19 +61,19 @@ void test_seq_cst(StoreOp store_op, LoadOp load_op) {
     auto t2 = support::make_test_thread([&] { store_op(y, old_value, new_value); });
 
     auto t3 = support::make_test_thread([&] {
-      while (!equals(load_op(x), new_value)) {
+      while (!equals(load_op(x), const_cast<std::remove_cv_t<T> const&>(new_value))) {
         std::this_thread::yield();
       }
-      if (!equals(load_op(y), new_value)) {
+      if (!equals(load_op(y), const_cast<std::remove_cv_t<T> const&>(new_value))) {
         x_updated_first.store(true, std::memory_order_relaxed);
       }
     });
 
     auto t4 = support::make_test_thread([&] {
-      while (!equals(load_op(y), new_value)) {
+      while (!equals(load_op(y), const_cast<std::remove_cv_t<T> const&>(new_value))) {
         std::this_thread::yield();
       }
-      if (!equals(load_op(x), new_value)) {
+      if (!equals(load_op(x), const_cast<std::remove_cv_t<T> const&>(new_value))) {
         y_updated_first.store(true, std::memory_order_relaxed);
       }
     });
@@ -98,10 +98,10 @@ template <class T, class StoreOp, class LoadOp>
 void test_acquire_release(StoreOp store_op, LoadOp load_op) {
 #ifndef TEST_HAS_NO_THREADS
   for (auto i = 0; i < 100; ++i) {
-    T old_value(make_value<T>(0));
-    T new_value(make_value<T>(1));
+    T old_value(make_value<std::remove_cv_t<T>>(0));
+    T new_value(make_value<std::remove_cv_t<T>>(1));
 
-    T copy = old_value;
+    T copy = const_cast<std::remove_cv_t<T> const&>(old_value);
     std::atomic_ref<T> const at(copy);
     int non_atomic = 5;
 
@@ -110,14 +110,15 @@ void test_acquire_release(StoreOp store_op, LoadOp load_op) {
     threads.reserve(number_of_threads);
 
     for (auto j = 0; j < number_of_threads; ++j) {
-      threads.push_back(support::make_test_thread([&at, &non_atomic, load_op, new_value] {
-        while (!equals(load_op(at), new_value)) {
-          std::this_thread::yield();
-        }
-        // Other thread's writes before the release store are visible
-        // in this thread's read after the acquire load
-        assert(non_atomic == 6);
-      }));
+      threads.push_back(support::make_test_thread(
+          [&at, &non_atomic, load_op, new_value = const_cast<std::remove_cv_t<T> const&>(new_value)] {
+            while (!equals(load_op(at), new_value)) {
+              std::this_thread::yield();
+            }
+            // Other thread's writes before the release store are visible
+            // in this thread's read after the acquire load
+            assert(non_atomic == 6);
+          }));
     }
 
     non_atomic = 6;



More information about the libcxx-commits mailing list