[PATCH] libstdc++: Handle cv-qualified types in atomic and atomic_ref [PR115402]

Jonathan Wakely jwakely@redhat.com
Thu Oct 9 14:48:17 GMT 2025


On Fri, 26 Sep 2025 at 11:17 +0200, Tomasz Kamiński wrote:
>From: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
>
>Implements P3233R1 (DR for C++20/C++11, fixes LWG 4069 and 3508).
>
>This commit implements std::atomic_ref<cv T> support (LWG3508) as DR for
>C++20, by exctracting parts of the __atomic_ref class (that atomic_ref inherits

"extracting"

>rom) into a further base class (__atomic_ref_base):

"from"

>
>* __atomic_ref_base<const T> implements non-mutating (const) atomic API.
>  Single base class is used, and the difference in is_always_lock_free and
>  required_aligment values between types are handled by _S_is_always_lock_free,
>  _S_required_aligment helper functions.
>* __atomic_ref_base<T> implements the common atomic APIs. The non-mutating
>  operations are handled by inherting from __atomic_ref_base<const T> partial
>  partial specialization. Tu support that __atomic_ref_base<const T> stores
>  mutable pointer to T, and performs const_cast in constructor.
>* __atomic_ref<T, ....> inherits from __atomic_ref_base<T>, and implement
>  type-specific mutable APIs (fetch_add, -=, ...) and difference_type member
>  type.
>* __atomic_ref<const T, ...> inherits from __atomic_ref_base<const T>
>  and adds different_type member, whose presence and denoted type depends
>  on T.
>
>The __atomic_ref specialization selection is adjusted to handle cv-qualified
>bool (add remove_cv_t) and pointer types. To handle the later, additional
>constant template parameter is introduced.
>
>The atomic wait and notify operations are currently not supported for volatile
>types, to signal that static assert is added to corresponding methods of
>atomic_ref.
>
>At the same time,  disable support for cv-qualified types in std::atomic
>(for instance, std::atomic<volatile T> isn't meaningful; one should use
>volatile std::atomic<T>), again as per the paper, resolving LWG4069 as DR
>for C++11. This only affects atomic<volatile T>, as specialization
>atomic with const-qualifed types was already producing an compile-time
>error.
>
>	PR libstdc++/115402
>
>libstdc++-v3/ChangeLog:
>
>	* include/bits/atomic_base.h (__atomic_ref_base<const _Tp>)
>	(__atomic_ref_base<_Tp>): Define by extracting common methods
>	from atomic_ref specializations.
>	(__atomic_ref<_Tp, In, Fp, Pt>): Inherit from __atomic_ref_base
>	and remove extracted method.
>	(__atomic_ref<const _Tp, In, Fp, Pt>): Define.
>	* include/std/atomic (std::atomic): Added an
>	* testsuite/29_atomics/atomic/requirements/types_neg.cc:
>	Add test for volatile qualified types.
>	* testsuite/29_atomics/atomic_ref/bool.cc: Move the content
>	to op_support.cc, add test for bool.
>	* testsuite/29_atomics/atomic_ref/op_support.cc: New test
>	expanded from atomic_ref/bool.cc.
>	* testsuite/29_atomics/atomic_ref/cv_qual.cc: New test.
>	* testsuite/29_atomics/atomic_ref/requirements_neg.cc: New test.
>	* testsuite/29_atomics/atomic_ref/deduction.cc: Add tests for
>	cv-qualified types.
>	* testsuite/29_atomics/atomic_ref/float.cc: Likewise.
>	* testsuite/29_atomics/atomic_ref/generic.cc: Likewise.
>	* testsuite/29_atomics/atomic_ref/integral.cc: Likewise.
>	* testsuite/29_atomics/atomic_ref/pointer.cc: Likewise.
>	* testsuite/29_atomics/atomic_ref/requirements.cc: Likewise.
>	* testsuite/29_atomics/atomic_ref/wait_notify.cc: Add tests for
>	const qualified types.
>
>Co-authored-by: Tomasz Kamiński <tkaminsk@redhat.com>
>Signed-off-by: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
>Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
>---
>The changes and split between original Giusepee patch, and my changes
>can be found here:
>https://forge.sourceware.org/gcc/gcc-TEST/pulls/85
>
>Tested on x86-64-linux locally. Testing on powerpc64le-linux.
>OK for trunk when test passed.
>
> libstdc++-v3/include/bits/atomic_base.h       | 566 ++++++------------
> libstdc++-v3/include/std/atomic               |   5 +
> .../atomic/requirements/types_neg.cc          |   4 +-
> .../testsuite/29_atomics/atomic_ref/bool.cc   |  94 ++-
> .../29_atomics/atomic_ref/cv_qual.cc          |  94 +++
> .../29_atomics/atomic_ref/deduction.cc        |  33 +-
> .../testsuite/29_atomics/atomic_ref/float.cc  |  21 +-
> .../29_atomics/atomic_ref/generic.cc          |   6 +
> .../29_atomics/atomic_ref/integral.cc         |   6 +
> .../29_atomics/atomic_ref/op_support.cc       | 113 ++++
> .../29_atomics/atomic_ref/pointer.cc          |   6 +
> .../29_atomics/atomic_ref/requirements.cc     |  68 ++-
> .../29_atomics/atomic_ref/requirements_neg.cc |  34 ++
> .../29_atomics/atomic_ref/wait_notify.cc      |  10 +
> 14 files changed, 621 insertions(+), 439 deletions(-)
> create mode 100644 libstdc++-v3/testsuite/29_atomics/atomic_ref/cv_qual.cc
> create mode 100644 libstdc++-v3/testsuite/29_atomics/atomic_ref/op_support.cc
> create mode 100644 libstdc++-v3/testsuite/29_atomics/atomic_ref/requirements_neg.cc
>
>diff --git a/libstdc++-v3/include/bits/atomic_base.h b/libstdc++-v3/include/bits/atomic_base.h
>index 92d1269493f..5b8c2d49e9b 100644
>--- a/libstdc++-v3/include/bits/atomic_base.h
>+++ b/libstdc++-v3/include/bits/atomic_base.h
>@@ -1508,211 +1508,146 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>     };
> #undef _GLIBCXX20_INIT
>
>-  template<typename _Tp,
>-           bool = is_integral_v<_Tp> && !is_same_v<_Tp, bool>,
>-           bool = is_floating_point_v<_Tp>>
>-    struct __atomic_ref;
>+  // __atomic_ref_base<const _Tp> provides the common APIs for const and
>+  // types,

Should this be "const and non-const types"?

>+  // __atomic_ref_base<_Tp> inhserits from  __atomic_ref_base<const _Tp>,

"inherits"

>+  // and provides the commonn APIs implementing constrains in [atomic.ref].

"common" and "constraints"

>+  // __atomic_ref<_Tp> inherits from __atomic_ref_base<_Tp> (const or not-const)

"non-const"

>+  // adds type specific mutating APIs.

Hyphen in "type-specific" please

>+  // atomic_ref inherits from __atomic_ref;
>+
>+  template<typename _Tp>
>+    struct __atomic_ref_base;
>
>-  // base class for non-integral, non-floating-point, non-pointer types
>   template<typename _Tp>
>-    struct __atomic_ref<_Tp, false, false>
>+    struct __atomic_ref_base<const _Tp>
>     {
>-      static_assert(is_trivially_copyable_v<_Tp>);
>+    private:
>+      using _Vt = remove_cv_t<_Tp>;
>+
>+      static consteval bool
>+      _S_is_always_lock_free()
>+      {
>+	if constexpr (is_pointer_v<_Vt>)
>+	  return ATOMIC_POINTER_LOCK_FREE == 2;
>+	else
>+	  return __atomic_always_lock_free(sizeof(_Vt), 0);
>+      }
>
>-      // 1/2/4/8/16-byte types must be aligned to at least their size.
>-      static constexpr int _S_min_alignment
>-	= (sizeof(_Tp) & (sizeof(_Tp) - 1)) || sizeof(_Tp) > 16
>-	? 0 : sizeof(_Tp);
>+      static consteval int
>+      _S_required_aligment()
>+      {
>+	if constexpr (is_floating_point_v<_Vt> || is_pointer_v<_Vt>)
>+	  return alignof(_Vt);
>+	else if constexpr ((sizeof(_Vt) & (sizeof(_Vt) - 1)) || sizeof(_Vt) > 16)
>+	  return alignof(_Vt);
>+	else
>+	  // 1/2/4/8/16-byte types, including integral types,
>+	  // must be aligned to at least their size.
>+	  return (sizeof(_Vt) > alignof(_Vt)) ? sizeof(_Vt) : alignof(_Vt);
>+      }
>
>     public:
>-      using value_type = _Tp;
>+      using value_type = _Vt;
>+      static_assert(is_trivially_copyable_v<value_type>);
>
>-      static constexpr bool is_always_lock_free
>-	= __atomic_always_lock_free(sizeof(_Tp), 0);
>+      static constexpr bool is_always_lock_free = _S_is_always_lock_free();
>+      static_assert(is_always_lock_free || !is_volatile_v<_Tp>,
>+	"atomic_ref of volatile-qualified type is only sypported if operations are lock-free");

"supported", butI'd like to phrase this as a "must be ..." sentence
rather than "is only supported".

How about:

"atomic operations on volatile T must be lock-free"

>
>-      static constexpr size_t required_alignment
>-	= _S_min_alignment > alignof(_Tp) ? _S_min_alignment : alignof(_Tp);
>+      static constexpr size_t required_alignment = _S_required_aligment();
>
>-      __atomic_ref& operator=(const __atomic_ref&) = delete;
>+      __atomic_ref_base() = delete;
>+      __atomic_ref_base& operator=(const __atomic_ref_base&) = delete;
>
>       explicit
>-      __atomic_ref(_Tp& __t) : _M_ptr(std::__addressof(__t))
>+      __atomic_ref_base(const _Tp& __t)
>+	: _M_ptr(const_cast<_Tp*>(std::__addressof(__t)))

This can be replaced with std::addressof (previously __addressof was
more efficient but we fixed that).

>       {
> 	__glibcxx_assert(((__UINTPTR_TYPE__)_M_ptr % required_alignment) == 0);
>       }
>
>-      __atomic_ref(const __atomic_ref&) noexcept = default;
>-
>-      _Tp
>-      operator=(_Tp __t) const noexcept
>-      {
>-	this->store(__t);
>-	return __t;
>-      }
>+      __atomic_ref_base(const __atomic_ref_base&) noexcept = default;
>
>-      operator _Tp() const noexcept { return this->load(); }
>+      operator value_type() const noexcept { return this->load(); }
>
>       bool
>       is_lock_free() const noexcept
>       { return __atomic_impl::is_lock_free<sizeof(_Tp), required_alignment>(); }
>
>-      void
>-      store(_Tp __t, memory_order __m = memory_order_seq_cst) const noexcept
>-      { __atomic_impl::store(_M_ptr, __t, __m); }
>-
>-      _Tp
>+      value_type
>       load(memory_order __m = memory_order_seq_cst) const noexcept
>       { return __atomic_impl::load(_M_ptr, __m); }
>
>-      _Tp
>-      exchange(_Tp __desired, memory_order __m = memory_order_seq_cst)
>-      const noexcept
>-      { return __atomic_impl::exchange(_M_ptr, __desired, __m); }
>-
>-      bool
>-      compare_exchange_weak(_Tp& __expected, _Tp __desired,
>-			    memory_order __success,
>-			    memory_order __failure) const noexcept
>-      {
>-	return __atomic_impl::compare_exchange_weak<true>(
>-		 _M_ptr, __expected, __desired, __success, __failure);
>-      }
>-
>-      bool
>-      compare_exchange_strong(_Tp& __expected, _Tp __desired,
>-			    memory_order __success,
>-			    memory_order __failure) const noexcept
>-      {
>-	return __atomic_impl::compare_exchange_strong<true>(
>-		 _M_ptr, __expected, __desired, __success, __failure);
>-      }
>-
>-      bool
>-      compare_exchange_weak(_Tp& __expected, _Tp __desired,
>-			    memory_order __order = memory_order_seq_cst)
>-      const noexcept
>-      {
>-	return compare_exchange_weak(__expected, __desired, __order,
>-                                     __cmpexch_failure_order(__order));
>-      }
>-
>-      bool
>-      compare_exchange_strong(_Tp& __expected, _Tp __desired,
>-			      memory_order __order = memory_order_seq_cst)
>-      const noexcept
>-      {
>-	return compare_exchange_strong(__expected, __desired, __order,
>-				       __cmpexch_failure_order(__order));
>-      }
>-
> #if __glibcxx_atomic_wait
>       _GLIBCXX_ALWAYS_INLINE void
>-      wait(_Tp __old, memory_order __m = memory_order_seq_cst) const noexcept
>-      { __atomic_impl::wait(_M_ptr, __old, __m); }
>-
>-      // TODO add const volatile overload
>-
>-      _GLIBCXX_ALWAYS_INLINE void
>-      notify_one() const noexcept
>-      { __atomic_impl::notify_one(_M_ptr); }
>-
>-      // TODO add const volatile overload
>-
>-      _GLIBCXX_ALWAYS_INLINE void
>-      notify_all() const noexcept
>-      { __atomic_impl::notify_all(_M_ptr); }
>-
>-      // TODO add const volatile overload
>+      wait(value_type __old, memory_order __m = memory_order_seq_cst) const noexcept
>+      {
>+	// TODO remove when volatile is supported
>+	static_assert(!is_volatile_v<_Tp>, "atomics wait on volatile are not supported");

Since this static assert is about a missing feature in the
implementation, I think the wording doesn't need to follow the usual
"must" form, but it should be "atomic waits" not "atomics wait" (same
change in two more places below).

>+	__atomic_impl::wait(_M_ptr, __old, __m);
>+      }
> #endif // __glibcxx_atomic_wait
>
>-    private:
>+    protected:
>       _Tp* _M_ptr;
>     };
>
>-  // base class for atomic_ref<integral-type>
>   template<typename _Tp>
>-    struct __atomic_ref<_Tp, true, false>
>+    struct __atomic_ref_base
>+      : __atomic_ref_base<const _Tp>
>     {
>-      static_assert(is_integral_v<_Tp>);
>-
>-    public:
>-      using value_type = _Tp;
>-      using difference_type = value_type;
>-
>-      static constexpr bool is_always_lock_free
>-	= __atomic_always_lock_free(sizeof(_Tp), 0);
>-
>-      static constexpr size_t required_alignment
>-	= sizeof(_Tp) > alignof(_Tp) ? sizeof(_Tp) : alignof(_Tp);
>-
>-      __atomic_ref() = delete;
>-      __atomic_ref& operator=(const __atomic_ref&) = delete;
>+      using value_type = typename __atomic_ref_base<const _Tp>::value_type;
>
>       explicit
>-      __atomic_ref(_Tp& __t) : _M_ptr(&__t)
>-      {
>-	__glibcxx_assert(((__UINTPTR_TYPE__)_M_ptr % required_alignment) == 0);
>-      }
>+      __atomic_ref_base(_Tp& __t) : __atomic_ref_base<const _Tp>(__t)
>+      { }
>
>-      __atomic_ref(const __atomic_ref&) noexcept = default;
>-
>-      _Tp
>-      operator=(_Tp __t) const noexcept
>+      value_type
>+      operator=(value_type __t) const noexcept
>       {
> 	this->store(__t);
> 	return __t;
>       }
>
>-      operator _Tp() const noexcept { return this->load(); }
>-
>-      bool
>-      is_lock_free() const noexcept
>-      {
>-	return __atomic_impl::is_lock_free<sizeof(_Tp), required_alignment>();
>-      }
>-
>       void
>-      store(_Tp __t, memory_order __m = memory_order_seq_cst) const noexcept
>-      { __atomic_impl::store(_M_ptr, __t, __m); }
>-
>-      _Tp
>-      load(memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::load(_M_ptr, __m); }
>+      store(value_type __t, memory_order __m = memory_order_seq_cst) const noexcept
>+      { __atomic_impl::store(this->_M_ptr, __t, __m); }
>
>-      _Tp
>-      exchange(_Tp __desired,
>-	       memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::exchange(_M_ptr, __desired, __m); }
>+      value_type
>+      exchange(value_type __desired, memory_order __m = memory_order_seq_cst)
>+      const noexcept
>+      { return __atomic_impl::exchange(this->_M_ptr, __desired, __m); }
>
>       bool
>-      compare_exchange_weak(_Tp& __expected, _Tp __desired,
>+      compare_exchange_weak(value_type& __expected, value_type __desired,
> 			    memory_order __success,
> 			    memory_order __failure) const noexcept
>       {
> 	return __atomic_impl::compare_exchange_weak<true>(
>-		 _M_ptr, __expected, __desired, __success, __failure);
>+		 this->_M_ptr, __expected, __desired, __success, __failure);
>       }
>
>       bool
>-      compare_exchange_strong(_Tp& __expected, _Tp __desired,
>-			      memory_order __success,
>-			      memory_order __failure) const noexcept
>+      compare_exchange_strong(value_type& __expected, value_type __desired,
>+			    memory_order __success,
>+			    memory_order __failure) const noexcept
>       {
> 	return __atomic_impl::compare_exchange_strong<true>(
>-		 _M_ptr, __expected, __desired, __success, __failure);
>+		 this->_M_ptr, __expected, __desired, __success, __failure);
>       }
>
>       bool
>-      compare_exchange_weak(_Tp& __expected, _Tp __desired,
>+      compare_exchange_weak(value_type& __expected, value_type __desired,
> 			    memory_order __order = memory_order_seq_cst)
>       const noexcept
>       {
> 	return compare_exchange_weak(__expected, __desired, __order,
>-                                     __cmpexch_failure_order(__order));
>+				     __cmpexch_failure_order(__order));
>       }
>
>       bool
>-      compare_exchange_strong(_Tp& __expected, _Tp __desired,
>+      compare_exchange_strong(value_type& __expected, value_type __desired,
> 			      memory_order __order = memory_order_seq_cst)
>       const noexcept
>       {
>@@ -1721,49 +1656,81 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       }
>
> #if __glibcxx_atomic_wait
>-      _GLIBCXX_ALWAYS_INLINE void
>-      wait(_Tp __old, memory_order __m = memory_order_seq_cst) const noexcept
>-      { __atomic_impl::wait(_M_ptr, __old, __m); }
>-
>-      // TODO add const volatile overload
>-
>       _GLIBCXX_ALWAYS_INLINE void
>       notify_one() const noexcept
>-      { __atomic_impl::notify_one(_M_ptr); }
>-
>-      // TODO add const volatile overload
>+      {
>+	// TODO remove when volatile is supported
>+	static_assert(!is_volatile_v<_Tp>, "atomics wait on volatile are not supported");
>+	__atomic_impl::notify_one(this->_M_ptr);
>+      }
>
>       _GLIBCXX_ALWAYS_INLINE void
>       notify_all() const noexcept
>-      { __atomic_impl::notify_all(_M_ptr); }
>-
>-      // TODO add const volatile overload
>+      {
>+	// TODO remove when volatile is supported
>+	static_assert(!is_volatile_v<_Tp>, "atomics wait on volatile are not supported");
>+	__atomic_impl::notify_all(this->_M_ptr);
>+      }
> #endif // __glibcxx_atomic_wait
>+    };
>+
>+  template<typename _Tp,
>+	   bool = is_integral_v<_Tp> && !is_same_v<remove_cv_t<_Tp>, bool>,
>+	   bool = is_floating_point_v<_Tp>,
>+	   bool = is_pointer_v<_Tp>>
>+    struct __atomic_ref;
>+
>+  // base class for non-integral, non-floating-point, non-pointer types
>+  template<typename _Tp>
>+    struct __atomic_ref<_Tp, false, false, false>
>+      : __atomic_ref_base<_Tp>
>+    {
>+      using __atomic_ref_base<_Tp>::__atomic_ref_base;
>+      using __atomic_ref_base<_Tp>::operator=;
>+    };
>+
>+  template<typename _Tp>
>+    struct __atomic_ref<const _Tp, false, false, false>
>+      : __atomic_ref_base<const _Tp>
>+    {
>+      using __atomic_ref_base<const _Tp>::__atomic_ref_base;
>+    };
>+
>+  // base class for atomic_ref<integral-type>
>+  template<typename _Tp>
>+    struct __atomic_ref<_Tp, true, false, false>
>+      : __atomic_ref_base<_Tp>
>+    {
>+      using value_type = typename __atomic_ref_base<_Tp>::value_type;
>+      using difference_type = value_type;
>+
>+      using __atomic_ref_base<_Tp>::__atomic_ref_base;
>+      using __atomic_ref_base<_Tp>::operator=;
>
>       value_type
>       fetch_add(value_type __i,
> 		memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::fetch_add(_M_ptr, __i, __m); }
>+      { return __atomic_impl::fetch_add(this->_M_ptr, __i, __m); }
>
>       value_type
>       fetch_sub(value_type __i,
> 		memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::fetch_sub(_M_ptr, __i, __m); }
>+      { return __atomic_impl::fetch_sub(this->_M_ptr, __i, __m); }
>
>       value_type
>       fetch_and(value_type __i,
> 		memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::fetch_and(_M_ptr, __i, __m); }
>+      { return __atomic_impl::fetch_and(this->_M_ptr, __i, __m); }
>
>       value_type
>       fetch_or(value_type __i,
> 	       memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::fetch_or(_M_ptr, __i, __m); }
>+      { return __atomic_impl::fetch_or(this->_M_ptr, __i, __m); }
>
>       value_type
>       fetch_xor(value_type __i,
> 		memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::fetch_xor(_M_ptr, __i, __m); }
>+      { return __atomic_impl::fetch_xor(this->_M_ptr, __i, __m); }
>
>       _GLIBCXX_ALWAYS_INLINE value_type
>       operator++(int) const noexcept
>@@ -1775,284 +1742,98 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
>       value_type
>       operator++() const noexcept
>-      { return __atomic_impl::__add_fetch(_M_ptr, value_type(1)); }
>+      { return __atomic_impl::__add_fetch(this->_M_ptr, value_type(1)); }
>
>       value_type
>       operator--() const noexcept
>-      { return __atomic_impl::__sub_fetch(_M_ptr, value_type(1)); }
>+      { return __atomic_impl::__sub_fetch(this->_M_ptr, value_type(1)); }
>
>       value_type
>       operator+=(value_type __i) const noexcept
>-      { return __atomic_impl::__add_fetch(_M_ptr, __i); }
>+      { return __atomic_impl::__add_fetch(this->_M_ptr, __i); }
>
>       value_type
>       operator-=(value_type __i) const noexcept
>-      { return __atomic_impl::__sub_fetch(_M_ptr, __i); }
>+      { return __atomic_impl::__sub_fetch(this->_M_ptr, __i); }
>
>       value_type
>       operator&=(value_type __i) const noexcept
>-      { return __atomic_impl::__and_fetch(_M_ptr, __i); }
>+      { return __atomic_impl::__and_fetch(this->_M_ptr, __i); }
>
>       value_type
>       operator|=(value_type __i) const noexcept
>-      { return __atomic_impl::__or_fetch(_M_ptr, __i); }
>+      { return __atomic_impl::__or_fetch(this->_M_ptr, __i); }
>
>       value_type
>       operator^=(value_type __i) const noexcept
>-      { return __atomic_impl::__xor_fetch(_M_ptr, __i); }
>+      { return __atomic_impl::__xor_fetch(this->_M_ptr, __i); }
>+    };
>
>-    private:
>-      _Tp* _M_ptr;
>+  template<typename _Tp>
>+    struct __atomic_ref<const _Tp, true, false, false>
>+      : __atomic_ref_base<const _Tp>
>+    {
>+      using difference_type = typename __atomic_ref_base<const _Tp>::value_type;
>+      using __atomic_ref_base<const _Tp>::__atomic_ref_base;
>     };
>
>   // base class for atomic_ref<floating-point-type>
>   template<typename _Fp>
>-    struct __atomic_ref<_Fp, false, true>
>+    struct __atomic_ref<_Fp, false, true, false>
>+      : __atomic_ref_base<_Fp>
>     {
>-      static_assert(is_floating_point_v<_Fp>);
>-
>-    public:
>-      using value_type = _Fp;
>+      using value_type = typename __atomic_ref_base<_Fp>::value_type;
>       using difference_type = value_type;
>
>-      static constexpr bool is_always_lock_free
>-	= __atomic_always_lock_free(sizeof(_Fp), 0);
>-
>-      static constexpr size_t required_alignment = __alignof__(_Fp);
>-
>-      __atomic_ref() = delete;
>-      __atomic_ref& operator=(const __atomic_ref&) = delete;
>-
>-      explicit
>-      __atomic_ref(_Fp& __t) : _M_ptr(&__t)
>-      {
>-	__glibcxx_assert(((__UINTPTR_TYPE__)_M_ptr % required_alignment) == 0);
>-      }
>-
>-      __atomic_ref(const __atomic_ref&) noexcept = default;
>-
>-      _Fp
>-      operator=(_Fp __t) const noexcept
>-      {
>-	this->store(__t);
>-	return __t;
>-      }
>-
>-      operator _Fp() const noexcept { return this->load(); }
>-
>-      bool
>-      is_lock_free() const noexcept
>-      {
>-	return __atomic_impl::is_lock_free<sizeof(_Fp), required_alignment>();
>-      }
>-
>-      void
>-      store(_Fp __t, memory_order __m = memory_order_seq_cst) const noexcept
>-      { __atomic_impl::store(_M_ptr, __t, __m); }
>-
>-      _Fp
>-      load(memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::load(_M_ptr, __m); }
>-
>-      _Fp
>-      exchange(_Fp __desired,
>-	       memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::exchange(_M_ptr, __desired, __m); }
>-
>-      bool
>-      compare_exchange_weak(_Fp& __expected, _Fp __desired,
>-			    memory_order __success,
>-			    memory_order __failure) const noexcept
>-      {
>-	return __atomic_impl::compare_exchange_weak<true>(
>-		 _M_ptr, __expected, __desired, __success, __failure);
>-      }
>-
>-      bool
>-      compare_exchange_strong(_Fp& __expected, _Fp __desired,
>-			      memory_order __success,
>-			      memory_order __failure) const noexcept
>-      {
>-	return __atomic_impl::compare_exchange_strong<true>(
>-		 _M_ptr, __expected, __desired, __success, __failure);
>-      }
>-
>-      bool
>-      compare_exchange_weak(_Fp& __expected, _Fp __desired,
>-			    memory_order __order = memory_order_seq_cst)
>-      const noexcept
>-      {
>-	return compare_exchange_weak(__expected, __desired, __order,
>-                                     __cmpexch_failure_order(__order));
>-      }
>-
>-      bool
>-      compare_exchange_strong(_Fp& __expected, _Fp __desired,
>-			      memory_order __order = memory_order_seq_cst)
>-      const noexcept
>-      {
>-	return compare_exchange_strong(__expected, __desired, __order,
>-				       __cmpexch_failure_order(__order));
>-      }
>-
>-#if __glibcxx_atomic_wait
>-      _GLIBCXX_ALWAYS_INLINE void
>-      wait(_Fp __old, memory_order __m = memory_order_seq_cst) const noexcept
>-      { __atomic_impl::wait(_M_ptr, __old, __m); }
>-
>-      // TODO add const volatile overload
>-
>-      _GLIBCXX_ALWAYS_INLINE void
>-      notify_one() const noexcept
>-      { __atomic_impl::notify_one(_M_ptr); }
>-
>-      // TODO add const volatile overload
>-
>-      _GLIBCXX_ALWAYS_INLINE void
>-      notify_all() const noexcept
>-      { __atomic_impl::notify_all(_M_ptr); }
>-
>-      // TODO add const volatile overload
>-#endif // __glibcxx_atomic_wait
>+      using __atomic_ref_base<_Fp>::__atomic_ref_base;
>+      using __atomic_ref_base<_Fp>::operator=;
>
>       value_type
>       fetch_add(value_type __i,
> 		memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::__fetch_add_flt(_M_ptr, __i, __m); }
>+      { return __atomic_impl::__fetch_add_flt(this->_M_ptr, __i, __m); }
>
>       value_type
>       fetch_sub(value_type __i,
> 		memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::__fetch_sub_flt(_M_ptr, __i, __m); }
>+      { return __atomic_impl::__fetch_sub_flt(this->_M_ptr, __i, __m); }
>
>       value_type
>       operator+=(value_type __i) const noexcept
>-      { return __atomic_impl::__add_fetch_flt(_M_ptr, __i); }
>+      { return __atomic_impl::__add_fetch_flt(this->_M_ptr, __i); }
>
>       value_type
>       operator-=(value_type __i) const noexcept
>-      { return __atomic_impl::__sub_fetch_flt(_M_ptr, __i); }
>+      { return __atomic_impl::__sub_fetch_flt(this->_M_ptr, __i); }
>+    };
>
>-    private:
>-      _Fp* _M_ptr;
>+  template<typename _Fp>
>+    struct __atomic_ref<const _Fp, false, true, false>
>+      : __atomic_ref_base<const _Fp>
>+    {
>+      using difference_type = typename __atomic_ref_base<const _Fp>::value_type;
>+      using __atomic_ref_base<const _Fp>::__atomic_ref_base;
>     };
>
>   // base class for atomic_ref<pointer-type>
>-  template<typename _Tp>
>-    struct __atomic_ref<_Tp*, false, false>
>+  template<typename _Pt>
>+    struct __atomic_ref<_Pt, false, false, true>
>+      : __atomic_ref_base<_Pt>
>     {
>-    public:
>-      using value_type = _Tp*;
>+      using value_type = typename __atomic_ref_base<_Pt>::value_type;
>       using difference_type = ptrdiff_t;
>
>-      static constexpr bool is_always_lock_free = ATOMIC_POINTER_LOCK_FREE == 2;
>-
>-      static constexpr size_t required_alignment = __alignof__(_Tp*);
>-
>-      __atomic_ref() = delete;
>-      __atomic_ref& operator=(const __atomic_ref&) = delete;
>-
>-      explicit
>-      __atomic_ref(_Tp*& __t) : _M_ptr(std::__addressof(__t))
>-      {
>-	__glibcxx_assert(((__UINTPTR_TYPE__)_M_ptr % required_alignment) == 0);
>-      }
>-
>-      __atomic_ref(const __atomic_ref&) noexcept = default;
>-
>-      _Tp*
>-      operator=(_Tp* __t) const noexcept
>-      {
>-	this->store(__t);
>-	return __t;
>-      }
>-
>-      operator _Tp*() const noexcept { return this->load(); }
>-
>-      bool
>-      is_lock_free() const noexcept
>-      {
>-	return __atomic_impl::is_lock_free<sizeof(_Tp*), required_alignment>();
>-      }
>-
>-      void
>-      store(_Tp* __t, memory_order __m = memory_order_seq_cst) const noexcept
>-      { __atomic_impl::store(_M_ptr, __t, __m); }
>-
>-      _Tp*
>-      load(memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::load(_M_ptr, __m); }
>-
>-      _Tp*
>-      exchange(_Tp* __desired,
>-	       memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::exchange(_M_ptr, __desired, __m); }
>-
>-      bool
>-      compare_exchange_weak(_Tp*& __expected, _Tp* __desired,
>-			    memory_order __success,
>-			    memory_order __failure) const noexcept
>-      {
>-	return __atomic_impl::compare_exchange_weak<true>(
>-		 _M_ptr, __expected, __desired, __success, __failure);
>-      }
>-
>-      bool
>-      compare_exchange_strong(_Tp*& __expected, _Tp* __desired,
>-			    memory_order __success,
>-			    memory_order __failure) const noexcept
>-      {
>-	return __atomic_impl::compare_exchange_strong<true>(
>-		 _M_ptr, __expected, __desired, __success, __failure);
>-      }
>-
>-      bool
>-      compare_exchange_weak(_Tp*& __expected, _Tp* __desired,
>-			    memory_order __order = memory_order_seq_cst)
>-      const noexcept
>-      {
>-	return compare_exchange_weak(__expected, __desired, __order,
>-                                     __cmpexch_failure_order(__order));
>-      }
>-
>-      bool
>-      compare_exchange_strong(_Tp*& __expected, _Tp* __desired,
>-			      memory_order __order = memory_order_seq_cst)
>-      const noexcept
>-      {
>-	return compare_exchange_strong(__expected, __desired, __order,
>-				       __cmpexch_failure_order(__order));
>-      }
>-
>-#if __glibcxx_atomic_wait
>-      _GLIBCXX_ALWAYS_INLINE void
>-      wait(_Tp* __old, memory_order __m = memory_order_seq_cst) const noexcept
>-      { __atomic_impl::wait(_M_ptr, __old, __m); }
>-
>-      // TODO add const volatile overload
>-
>-      _GLIBCXX_ALWAYS_INLINE void
>-      notify_one() const noexcept
>-      { __atomic_impl::notify_one(_M_ptr); }
>-
>-      // TODO add const volatile overload
>-
>-      _GLIBCXX_ALWAYS_INLINE void
>-      notify_all() const noexcept
>-      { __atomic_impl::notify_all(_M_ptr); }
>-
>-      // TODO add const volatile overload
>-#endif // __glibcxx_atomic_wait
>-
>+      using __atomic_ref_base<_Pt>::__atomic_ref_base;
>+      using __atomic_ref_base<_Pt>::operator=;
>       _GLIBCXX_ALWAYS_INLINE value_type
>       fetch_add(difference_type __d,
> 		memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::fetch_add(_M_ptr, _S_type_size(__d), __m); }
>+      { return __atomic_impl::fetch_add(this->_M_ptr, _S_type_size(__d), __m); }
>
>       _GLIBCXX_ALWAYS_INLINE value_type
>       fetch_sub(difference_type __d,
> 		memory_order __m = memory_order_seq_cst) const noexcept
>-      { return __atomic_impl::fetch_sub(_M_ptr, _S_type_size(__d), __m); }
>+      { return __atomic_impl::fetch_sub(this->_M_ptr, _S_type_size(__d), __m); }
>
>       value_type
>       operator++(int) const noexcept
>@@ -2065,36 +1846,43 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       value_type
>       operator++() const noexcept
>       {
>-	return __atomic_impl::__add_fetch(_M_ptr, _S_type_size(1));
>+	return __atomic_impl::__add_fetch(this->_M_ptr, _S_type_size(1));
>       }
>
>       value_type
>       operator--() const noexcept
>       {
>-	return __atomic_impl::__sub_fetch(_M_ptr, _S_type_size(1));
>+	return __atomic_impl::__sub_fetch(this->_M_ptr, _S_type_size(1));
>       }
>
>       value_type
>       operator+=(difference_type __d) const noexcept
>       {
>-	return __atomic_impl::__add_fetch(_M_ptr, _S_type_size(__d));
>+	return __atomic_impl::__add_fetch(this->_M_ptr, _S_type_size(__d));
>       }
>
>       value_type
>       operator-=(difference_type __d) const noexcept
>       {
>-	return __atomic_impl::__sub_fetch(_M_ptr, _S_type_size(__d));
>+	return __atomic_impl::__sub_fetch(this->_M_ptr, _S_type_size(__d));
>       }
>
>     private:
>       static constexpr ptrdiff_t
>       _S_type_size(ptrdiff_t __d) noexcept
>       {
>-	static_assert(is_object_v<_Tp>);
>-	return __d * sizeof(_Tp);
>+	using _Et = remove_pointer_t<value_type>;
>+	static_assert(is_object_v<_Et>);
>+	return __d * sizeof(_Et);
>       }
>+    };
>
>-      _Tp** _M_ptr;
>+  template<typename _Pt>
>+    struct __atomic_ref<const _Pt, false, false, true>
>+      : __atomic_ref_base<const _Pt>
>+    {
>+      using difference_type = ptrdiff_t;
>+      using __atomic_ref_base<const _Pt>::__atomic_ref_base;
>     };
> #endif // C++2a
>
>diff --git a/libstdc++-v3/include/std/atomic b/libstdc++-v3/include/std/atomic
>index 9b1aca0fc09..1ea28b1d742 100644
>--- a/libstdc++-v3/include/std/atomic
>+++ b/libstdc++-v3/include/std/atomic
>@@ -217,6 +217,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       static_assert(sizeof(_Tp) > 0,
> 		    "Incomplete or zero-sized types are not supported");
>
>+      // _GLIBCXX_RESOLVE_LIB_DEFECTS
>+      // 4069. std::atomic<volatile T> should be ill-formed
>+      static_assert(is_same<_Tp, typename remove_cv<_Tp>::type>::value,
>+		    "cv-qualified types are not supported");

Maybe:
"template argument for std::atomic must not be const or volatile"

OK with those changes, thanks (and thanks to Giuseppe).

>+
> #if __cplusplus > 201703L
>       static_assert(is_copy_constructible_v<_Tp>);
>       static_assert(is_move_constructible_v<_Tp>);
>diff --git a/libstdc++-v3/testsuite/29_atomics/atomic/requirements/types_neg.cc b/libstdc++-v3/testsuite/29_atomics/atomic/requirements/types_neg.cc
>index cfe44d255ca..b9105481006 100644
>--- a/libstdc++-v3/testsuite/29_atomics/atomic/requirements/types_neg.cc
>+++ b/libstdc++-v3/testsuite/29_atomics/atomic/requirements/types_neg.cc
>@@ -19,7 +19,9 @@
>
> #include <atomic>
>
>-std::atomic<const int> a; // { dg-error "here" }
>+std::atomic<const int> ca; // { dg-error "here" }
>+std::atomic<volatile int> va; // { dg-error "here" }
>+std::atomic<const volatile int> cva; // { dg-error "here" }
> // { dg-error "assignment to read-only type" "" { target *-*-* } 0 }
>
> struct MoveOnly
>diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/bool.cc b/libstdc++-v3/testsuite/29_atomics/atomic_ref/bool.cc
>index 4702932627e..c73319010ee 100644
>--- a/libstdc++-v3/testsuite/29_atomics/atomic_ref/bool.cc
>+++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/bool.cc
>@@ -1,15 +1,85 @@
>-// { dg-do compile { target c++20 } }
>+// Copyright (C) 2019-2025 Free Software Foundation, Inc.
>+//
>+// This file is part of the GNU ISO C++ Library.  This library is free
>+// software; you can redistribute it and/or modify it under the
>+// terms of the GNU General Public License as published by the
>+// Free Software Foundation; either version 3, or (at your option)
>+// any later version.
>+
>+// This library is distributed in the hope that it will be useful,
>+// but WITHOUT ANY WARRANTY; without even the implied warranty of
>+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+// GNU General Public License for more details.
>+
>+// You should have received a copy of the GNU General Public License along
>+// with this library; see the file COPYING3.  If not see
>+// <http://www.gnu.org/licenses/>.
>+
>+// { dg-do run { target c++20 } }
>+// { dg-require-atomic-cmpxchg-word "" }
>+// { dg-add-options libatomic }
>
> #include <atomic>
>+#include <testsuite_hooks.h>
>+
>+void
>+test01()
>+{
>+  bool value;
>+
>+  {
>+    const auto mo = std::memory_order_relaxed;
>+    std::atomic_ref<bool> a(value);
>+    bool ok = a.is_lock_free();
>+    if constexpr (std::atomic_ref<bool>::is_always_lock_free)
>+      VERIFY( ok );
>+    a = false;
>+    VERIFY( !a.load() );
>+    VERIFY( !a.load(mo) );
>+    a.store(true);
>+    VERIFY( a.load() );
>+    auto v = a.exchange(false);
>+    VERIFY( !a.load() );
>+    VERIFY( v );
>+    v = a.exchange(true, mo);
>+    VERIFY( a.load() );
>+    VERIFY( !v );
>+
>+    auto expected = a.load();
>+    while (!a.compare_exchange_weak(expected, false, mo, mo))
>+    { /* weak form can fail spuriously */ }
>+    VERIFY( !a.load() );
>+    VERIFY( expected );
>+
>+    ok = a.compare_exchange_strong(expected, true);
>+    VERIFY( !ok && !a.load() && !expected );
>+
>+    ok = a.compare_exchange_strong(expected, true);
>+    VERIFY( ok && a.load() && !expected );
>+  }
>+}
>+
>+void
>+test02()
>+{
>+  bool b = false;
>+  std::atomic_ref<bool> a0(b);
>+  std::atomic_ref<bool> a1(b);
>+  std::atomic_ref<const bool> a1c(b);
>+  std::atomic_ref<volatile bool> a1v(b);
>+  std::atomic_ref<const volatile bool> a1cv(b);
>+  std::atomic_ref<bool> a2(a0);
>+  b = true;
>+  VERIFY( a1.load() );
>+  VERIFY( a1c.load() );
>+  VERIFY( a1v.load() );
>+  VERIFY( a1cv.load() );
>+  VERIFY( a2.load() );
>+}
>
>-template<class T> concept has_and = requires (T& a) { a &= false; };
>-template<class T> concept has_or = requires (T& a) { a |= false; };
>-template<class T> concept has_xor = requires (T& a) { a ^= false; };
>-template<class T> concept has_fetch_add = requires (T& a) { a.fetch_add(true); };
>-template<class T> concept has_fetch_sub = requires (T& a) { a.fetch_sub(true); };
>-
>-static_assert( not has_and<std::atomic_ref<bool>> );
>-static_assert( not has_or<std::atomic_ref<bool>> );
>-static_assert( not has_xor<std::atomic_ref<bool>> );
>-static_assert( not has_fetch_add<std::atomic_ref<bool>> );
>-static_assert( not has_fetch_sub<std::atomic_ref<bool>> );
>+int
>+main()
>+{
>+  test01();
>+  test02();
>+}
>diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/cv_qual.cc b/libstdc++-v3/testsuite/29_atomics/atomic_ref/cv_qual.cc
>new file mode 100644
>index 00000000000..dfc6a559945
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/cv_qual.cc
>@@ -0,0 +1,94 @@
>+// Copyright (C) 2019-2025 Free Software Foundation, Inc.
>+//
>+// This file is part of the GNU ISO C++ Library.  This library is free
>+// software; you can redistribute it and/or modify it under the
>+// terms of the GNU General Public License as published by the
>+// Free Software Foundation; either version 3, or (at your option)
>+// any later version.
>+
>+// This library is distributed in the hope that it will be useful,
>+// but WITHOUT ANY WARRANTY; without even the implied warranty of
>+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+// GNU General Public License for more details.
>+
>+// You should have received a copy of the GNU General Public License along
>+// with this library; see the file COPYING3.  If not see
>+// <http://www.gnu.org/licenses/>.
>+
>+// { dg-do run { target c++20 } }
>+// { dg-require-atomic-cmpxchg-word "" }
>+// { dg-add-options libatomic }
>+
>+#include <atomic>
>+#include <testsuite_hooks.h>
>+
>+struct X
>+{
>+  X() = default;
>+  X(int i) : i(i) { }
>+  int i;
>+
>+  friend bool
>+  operator==(X, X) = default;
>+};
>+
>+template<typename V>
>+void
>+test01(V v0, V v1)
>+{
>+  V value;
>+
>+  if constexpr (std::atomic_ref<V>::is_always_lock_free)
>+  {
>+    std::atomic_ref<volatile V> a(value);
>+    VERIFY( a.is_lock_free() );
>+
>+    a = v0;
>+    VERIFY( V(a) == v0 );
>+    VERIFY( a.load() == v0 );
>+
>+    a.store(v1);
>+    VERIFY( a.load() == v1 );
>+
>+    V last = a.exchange(v0);
>+    VERIFY( a.load() == v0 );
>+    VERIFY( last == v1 );
>+
>+    V expected = a.load();
>+    while (!a.compare_exchange_weak(expected, v1))
>+    { /* weak form can fail spuriously */ }
>+    VERIFY( a.load() == v1 );
>+    VERIFY( expected == v0 );
>+
>+    bool ok;
>+    ok = a.compare_exchange_strong(expected, v0);
>+    VERIFY( !ok && a.load() == v1 && expected == v1 );
>+
>+    ok = a.compare_exchange_strong(expected, v0);
>+    VERIFY( ok && a.load() == v0 && expected == v1 );
>+
>+    std::atomic_ref<const volatile V> cva(value);
>+    VERIFY( cva.is_lock_free() );
>+    VERIFY( V(cva) == v0 );
>+    VERIFY( cva.load() == v0 );
>+  }
>+
>+  value = v0;
>+  std::atomic_ref<const V> ca(value);
>+  bool lf = ca.is_lock_free();
>+  if constexpr (std::atomic_ref<V>::is_always_lock_free)
>+    VERIFY( lf );
>+  VERIFY( V(ca) == v0 );
>+  VERIFY( ca.load() == v0 );
>+}
>+
>+int
>+main()
>+{
>+  int x;
>+  test01<bool>(false, true);
>+  test01<int>(1, 2);
>+  test01<float>(1.2, 3.4);
>+  test01<int*>(&x, &x+1);
>+  test01<X>(12, 13);
>+}
>diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/deduction.cc b/libstdc++-v3/testsuite/29_atomics/atomic_ref/deduction.cc
>index f67190e97a3..01dbfce2375 100644
>--- a/libstdc++-v3/testsuite/29_atomics/atomic_ref/deduction.cc
>+++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/deduction.cc
>@@ -19,22 +19,29 @@
>
> #include <atomic>
>
>+template <typename T>
> void
>-test01()
>+test_impl(T v)
> {
>-  int i = 0;
>-  std::atomic_ref a0(i);
>-  static_assert(std::is_same_v<decltype(a0), std::atomic_ref<int>>);
>-
>-  float f = 1.0f;
>-  std::atomic_ref a1(f);
>-  static_assert(std::is_same_v<decltype(a1), std::atomic_ref<float>>);
>+  std::atomic_ref a(v);
>+  static_assert(std::is_same_v<decltype(a), std::atomic_ref<T>>);
>+}
>
>-  int* p = &i;
>-  std::atomic_ref a2(p);
>-  static_assert(std::is_same_v<decltype(a2), std::atomic_ref<int*>>);
>+template <typename T>
>+void
>+test(T v)
>+{
>+  test_impl<T>(v);
>+  test_impl<const T>(v);
>+  test_impl<volatile T>(v);
>+  test_impl<const volatile T>(v);
>+}
>
>+int main()
>+{
>+  test<int>(0);
>+  test<float>(1.0f);
>+  test<int*>(nullptr);
>   struct X { } x;
>-  std::atomic_ref a3(x);
>-  static_assert(std::is_same_v<decltype(a3), std::atomic_ref<X>>);
>+  test<X>(x);
> }
>diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/float.cc b/libstdc++-v3/testsuite/29_atomics/atomic_ref/float.cc
>index 5773d144c36..c69f3a711d3 100644
>--- a/libstdc++-v3/testsuite/29_atomics/atomic_ref/float.cc
>+++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/float.cc
>@@ -299,14 +299,19 @@ test04()
> {
>   if constexpr (std::atomic_ref<float>::is_always_lock_free)
>   {
>-    float i = 0;
>-    float* ptr = 0;
>-    std::atomic_ref<float*> a0(ptr);
>-    std::atomic_ref<float*> a1(ptr);
>-    std::atomic_ref<float*> a2(a0);
>-    a0 = &i;
>-    VERIFY( a1 == &i );
>-    VERIFY( a2 == &i );
>+    float i = 0.0f;
>+    std::atomic_ref<float> a0(i);
>+    std::atomic_ref<float> a1(i);
>+    std::atomic_ref<const float> a1c(i);
>+    std::atomic_ref<volatile float> a1v(i);
>+    std::atomic_ref<const volatile float> a1cv(i);
>+    std::atomic_ref<float> a2(a0);
>+    a0 = 1.0f;
>+    VERIFY( a1 == 1.0f );
>+    VERIFY( a1c == 1.0f );
>+    VERIFY( a1v == 1.0f );
>+    VERIFY( a1cv == 1.0f );
>+    VERIFY( a2 == 1.0f );
>   }
> }
>
>diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/generic.cc b/libstdc++-v3/testsuite/29_atomics/atomic_ref/generic.cc
>index 2e6fa0f90e2..079ec1b1a78 100644
>--- a/libstdc++-v3/testsuite/29_atomics/atomic_ref/generic.cc
>+++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/generic.cc
>@@ -108,9 +108,15 @@ test02()
>   X i;
>   std::atomic_ref<X> a0(i);
>   std::atomic_ref<X> a1(i);
>+  std::atomic_ref<const X> a1c(i);
>+  std::atomic_ref<volatile X> a1v(i);
>+  std::atomic_ref<const volatile X> a1cv(i);
>   std::atomic_ref<X> a2(a0);
>   a0 = 42;
>   VERIFY( a1.load() == 42 );
>+  VERIFY( a1c.load() == 42 );
>+  VERIFY( a1v.load() == 42 );
>+  VERIFY( a1cv.load() == 42 );
>   VERIFY( a2.load() == 42 );
> }
>
>diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/integral.cc b/libstdc++-v3/testsuite/29_atomics/atomic_ref/integral.cc
>index f6b68ebc598..310434cefb5 100644
>--- a/libstdc++-v3/testsuite/29_atomics/atomic_ref/integral.cc
>+++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/integral.cc
>@@ -302,9 +302,15 @@ test03()
>   int i = 0;
>   std::atomic_ref<int> a0(i);
>   std::atomic_ref<int> a1(i);
>+  std::atomic_ref<const int> a1c(i);
>+  std::atomic_ref<volatile int> a1v(i);
>+  std::atomic_ref<const volatile int> a1cv(i);
>   std::atomic_ref<int> a2(a0);
>   a0 = 42;
>   VERIFY( a1 == 42 );
>+  VERIFY( a1c == 42 );
>+  VERIFY( a1v == 42 );
>+  VERIFY( a1cv == 42 );
>   VERIFY( a2 == 42 );
> }
>
>diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/op_support.cc b/libstdc++-v3/testsuite/29_atomics/atomic_ref/op_support.cc
>new file mode 100644
>index 00000000000..93c65dce263
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/op_support.cc
>@@ -0,0 +1,113 @@
>+// { dg-do compile { target c++20 } }
>+
>+#include <atomic>
>+
>+template<class T> concept has_and = requires (T& a) { a &= false; };
>+template<class T> concept has_or = requires (T& a) { a |= false; };
>+template<class T> concept has_xor = requires (T& a) { a ^= false; };
>+template<class T> concept has_fetch_add = requires (T& a) { a.fetch_add(true); };
>+template<class T> concept has_fetch_sub = requires (T& a) { a.fetch_sub(true); };
>+
>+static constexpr std::memory_order mo = std::memory_order_seq_cst;
>+
>+#define HAS(op) (requires (std::atomic_ref<T> a, T t) { op; })
>+
>+template<typename T>
>+void
>+no_stores()
>+{
>+  static_assert( !HAS(a = t) );
>+  static_assert( !HAS(a.store(t)) );
>+  static_assert( !HAS(a.store(t, mo)) );
>+  static_assert( !HAS(a.exchange(t)) );
>+  static_assert( !HAS(a.exchange(t, mo)) );
>+
>+  static_assert( !HAS(a.compare_exchange_weak(t, t)) );
>+  static_assert( !HAS(a.compare_exchange_weak(t, t, mo)) );
>+  static_assert( !HAS(a.compare_exchange_weak(t, t, mo, mo)) );
>+
>+  static_assert( !HAS(a.compare_exchange_strong(t, t)) );
>+  static_assert( !HAS(a.compare_exchange_strong(t, t, mo)) );
>+  static_assert( !HAS(a.compare_exchange_strong(t, t, mo, mo)) );
>+}
>+
>+template<typename T>
>+void
>+no_additions()
>+{
>+  static_assert( !HAS(a++) );
>+  static_assert( !HAS(++a) );
>+  static_assert( !HAS(a += t) );
>+  static_assert( !HAS(a.fetch_add(t)) );
>+  static_assert( !HAS(a.fetch_add(t, mo)) );
>+
>+  static_assert( !HAS(a--) );
>+  static_assert( !HAS(--a) );
>+  static_assert( !HAS(a -= t) );
>+  static_assert( !HAS(a.fetch_sub(t)) );
>+  static_assert( !HAS(a.fetch_sub(t, mo)) );
>+}
>+
>+template<typename T>
>+void
>+no_bitops()
>+{
>+  static_assert( !HAS(a &= t) );
>+  static_assert( !HAS(a.fetch_and(t)) );
>+  static_assert( !HAS(a.fetch_and(t, mo)) );
>+
>+  static_assert( !HAS(a |= t) );
>+  static_assert( !HAS(a.fetch_or(t)) );
>+  static_assert( !HAS(a.fetch_or(t, mo)) );
>+
>+  static_assert( !HAS(a ^= t) );
>+  static_assert( !HAS(a.fetch_xor(t)) );
>+  static_assert( !HAS(a.fetch_xor(t, mo)) );
>+}
>+
>+template<typename T>
>+void
>+no_math()
>+{
>+  no_additions<T>();
>+  no_bitops<T>();
>+}
>+
>+template<typename T>
>+void
>+no_mutations()
>+{
>+  no_stores<T>();
>+  no_math<T>();
>+}
>+
>+struct S
>+{
>+  int x;
>+  int y;
>+};
>+
>+int main()
>+{
>+  no_mutations<const int>();
>+  no_mutations<const volatile int>();
>+
>+  no_bitops<float>();
>+  no_bitops<volatile float>();
>+  no_mutations<const float>();
>+
>+  no_bitops<int*>();
>+  no_bitops<int* volatile>();
>+  no_mutations<int* const>();
>+  no_mutations<int* const volatile>();
>+
>+  no_math<bool>();
>+  no_math<volatile bool>();
>+  no_mutations<const bool>();
>+  no_mutations<const volatile bool>();
>+
>+  no_math<S>();
>+  no_math<volatile S>();
>+  no_mutations<const S>();
>+  no_mutations<const volatile S>();
>+}
>diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/pointer.cc b/libstdc++-v3/testsuite/29_atomics/atomic_ref/pointer.cc
>index d1789af890e..8db45c797c8 100644
>--- a/libstdc++-v3/testsuite/29_atomics/atomic_ref/pointer.cc
>+++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/pointer.cc
>@@ -210,9 +210,15 @@ test03()
>   int* ptr = 0;
>   std::atomic_ref<int*> a0(ptr);
>   std::atomic_ref<int*> a1(ptr);
>+  std::atomic_ref<int* const> a1c(ptr);
>+  std::atomic_ref<int* volatile> a1v(ptr);
>+  std::atomic_ref<int* const volatile> a1cv(ptr);
>   std::atomic_ref<int*> a2(a0);
>   a0 = &i;
>   VERIFY( a1 == &i );
>+  VERIFY( a1c == &i );
>+  VERIFY( a1v == &i );
>+  VERIFY( a1cv == &i );
>   VERIFY( a2 == &i );
> }
>
>diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/requirements.cc b/libstdc++-v3/testsuite/29_atomics/atomic_ref/requirements.cc
>index 3b929563a1e..8617661f8e1 100644
>--- a/libstdc++-v3/testsuite/29_atomics/atomic_ref/requirements.cc
>+++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/requirements.cc
>@@ -18,56 +18,92 @@
> // { dg-do compile { target c++20 } }
>
> #include <atomic>
>+#include <type_traits>
>
>+template <class T>
> void
>-test01()
>+test_generic()
> {
>-  struct X { int c; };
>-  using A = std::atomic_ref<X>;
>+  using A = std::atomic_ref<T>;
>   static_assert( std::is_standard_layout_v<A> );
>   static_assert( std::is_nothrow_copy_constructible_v<A> );
>   static_assert( std::is_trivially_destructible_v<A> );
>-  static_assert( std::is_same_v<A::value_type, X> );
>+  static_assert( std::is_same_v<typename A::value_type, std::remove_cv_t<T>> );
>+  static_assert( !requires { typename A::difference_type; } );
>   static_assert( !std::is_copy_assignable_v<A> );
>   static_assert( !std::is_move_assignable_v<A> );
> }
>
>+template <class T>
> void
>-test02()
>+test_integral()
> {
>-  using A = std::atomic_ref<int>;
>+  using A = std::atomic_ref<T>;
>   static_assert( std::is_standard_layout_v<A> );
>   static_assert( std::is_nothrow_copy_constructible_v<A> );
>   static_assert( std::is_trivially_destructible_v<A> );
>-  static_assert( std::is_same_v<A::value_type, int> );
>-  static_assert( std::is_same_v<A::difference_type, A::value_type> );
>+  static_assert( std::is_same_v<typename A::value_type, std::remove_cv_t<T>> );
>+  static_assert( std::is_same_v<typename A::difference_type, typename A::value_type> );
>   static_assert( !std::is_copy_assignable_v<A> );
>   static_assert( !std::is_move_assignable_v<A> );
> }
>
>+template <class T>
> void
>-test03()
>+test_floating_point()
> {
>-  using A = std::atomic_ref<double>;
>+  using A = std::atomic_ref<T>;
>   static_assert( std::is_standard_layout_v<A> );
>   static_assert( std::is_nothrow_copy_constructible_v<A> );
>   static_assert( std::is_trivially_destructible_v<A> );
>-  static_assert( std::is_same_v<A::value_type, double> );
>-  static_assert( std::is_same_v<A::difference_type, A::value_type> );
>+  static_assert( std::is_same_v<typename A::value_type, std::remove_cv_t<T>> );
>+  static_assert( std::is_same_v<typename A::difference_type, typename A::value_type> );
>   static_assert( !std::is_copy_assignable_v<A> );
>   static_assert( !std::is_move_assignable_v<A> );
> }
>
>+template <class T>
> void
>-test04()
>+test_pointer()
> {
>-  using A = std::atomic_ref<int*>;
>+  using A = std::atomic_ref<T>;
>   static_assert( std::is_standard_layout_v<A> );
>   static_assert( std::is_nothrow_copy_constructible_v<A> );
>   static_assert( std::is_trivially_destructible_v<A> );
>-  static_assert( std::is_same_v<A::value_type, int*> );
>-  static_assert( std::is_same_v<A::difference_type, std::ptrdiff_t> );
>+  static_assert( std::is_same_v<typename A::value_type, std::remove_cv_t<T>> );
>+  static_assert( std::is_same_v<typename A::difference_type, std::ptrdiff_t> );
>   static_assert( std::is_nothrow_copy_constructible_v<A> );
>   static_assert( !std::is_copy_assignable_v<A> );
>   static_assert( !std::is_move_assignable_v<A> );
> }
>+
>+int
>+main()
>+{
>+  struct X { int c; };
>+  test_generic<X>();
>+  test_generic<const X>();
>+  test_generic<volatile X>();
>+  test_generic<const volatile X>();
>+
>+  // atomic_ref excludes (cv) `bool` from the set of integral types
>+  test_generic<bool>();
>+  test_generic<const bool>();
>+  test_generic<volatile bool>();
>+  test_generic<const volatile bool>();
>+
>+  test_integral<int>();
>+  test_integral<const int>();
>+  test_integral<volatile int>();
>+  test_integral<const volatile int>();
>+
>+  test_floating_point<double>();
>+  test_floating_point<const double>();
>+  test_floating_point<volatile double>();
>+  test_floating_point<const volatile double>();
>+
>+  test_pointer<int*>();
>+  test_pointer<int* const>();
>+  test_pointer<int* volatile>();
>+  test_pointer<int* const volatile>();
>+}
>diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/requirements_neg.cc b/libstdc++-v3/testsuite/29_atomics/atomic_ref/requirements_neg.cc
>new file mode 100644
>index 00000000000..8b0abbde023
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/requirements_neg.cc
>@@ -0,0 +1,34 @@
>+// { dg-do compile { target c++20 } }
>+
>+#include <atomic>
>+
>+template<size_t N>
>+struct NonTrivial
>+{
>+  NonTrivial() = default;
>+  NonTrivial(NonTrivial const&) { };
>+};
>+
>+template<size_t N>
>+NonTrivial<N> ntv;
>+
>+std::atomic_ref<NonTrivial<0>> nt(ntv<0>); // { dg-error "here" }
>+std::atomic_ref<const NonTrivial<1>> cnt(ntv<1>); // { dg-error "here" }
>+std::atomic_ref<volatile NonTrivial<2>> vnt(ntv<2>); // { dg-error "here" }
>+std::atomic_ref<const volatile NonTrivial<3>> cvnt(ntv<3>); // { dg-error "here" }
>+
>+template<size_t N>
>+struct NonLockFree
>+{
>+  char c[1024 + N];
>+};
>+
>+template<size_t N>
>+NonLockFree<N> nlfv;
>+
>+std::atomic_ref<NonLockFree<0>> nlf(nlfv<0>);
>+std::atomic_ref<const NonLockFree<1>> cnlf(nlfv<1>);
>+std::atomic_ref<volatile NonLockFree<2>> vnlf(nlfv<2>); // { dg-error "here" }
>+std::atomic_ref<const volatile NonLockFree<3>> cvnlf(nlfv<3>); // { dg-error "here" }
>+
>+// { dg-error "static assertion failed" "" { target *-*-* } 0 }
>diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/wait_notify.cc b/libstdc++-v3/testsuite/29_atomics/atomic_ref/wait_notify.cc
>index ecabeecd5bb..db20a197ed0 100644
>--- a/libstdc++-v3/testsuite/29_atomics/atomic_ref/wait_notify.cc
>+++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/wait_notify.cc
>@@ -41,6 +41,16 @@ template<typename S>
>         });
>       a.wait(va);
>       t.join();
>+
>+      std::atomic_ref<const S> b{ aa };
>+      b.wait(va);
>+      std::thread t2([&]
>+        {
>+	  a.store(va);
>+	  a.notify_one();
>+        });
>+      b.wait(vb);
>+      t2.join();
>     }
>   }
>
>-- 
>2.51.0
>
>



More information about the Libstdc++ mailing list