This patch implements the P3860R1 (accepted as DR against C++20) and LWG4472.
The two constraints on the constructor (that T and U are similar types and
is_convertible_v<U*, T*>) are combined into a single check:
is_convertible_v<_Up(*)[1], _Tp(*)[1]>. While this check does is not equivalent
for array of know bound to array of unknown bound conversions (T[N] to T[]),
this is irrelevant for atomic_ref, since instantiation with an array type is
ill-formed (due to the return type of load and other members).
The __atomic_ref_base constructor is modified to accept _Tp* instead of _Tp&.
This allows both the atomic_ref(atomic_ref<_Up>) and atomic_ref(_Tp&)
constructors to delegate to it. The precondition check on alignment is moved
specifically to the atomic_ref(_Tp&) constructor, preventing redundant checks
during atomic_ref conversion.
A deleted atomic_ref(_Tp&&) constructor is introduced (per LWG4472).
This prevents the construction of atomic_ref<T> from atomic_ref<volatile T>
via the conversion operator.
libstdc++-v3/ChangeLog:
* include/bits/atomic_base.h
(__atomic_ref_base<const _Tp>::__atomic_ref_base): Accept
pointer instead of reference. Remove precondition check and
mark as noexcept.
(__atomic_ref_base<_Tp>::__atomic_ref_base): Accept pointer
insted of reference, and mark as noexcept.
* include/std/atomic (atomic_ref::atomic_ref(_Tp&)): Add
precondition check and take address of argument.
(atomic_ref::atomic_ref(_Tp&&)): Define as deleted.
(atomic_ref::atomic_ref(atomic_ref<_Up>)): Define.
* include/bits/shared_ptr_atomic.h (_Sp_atomic::_Atomic_count):
Pass address to __atomic_ref constructor.
* include/std/barrier (__tree_barrier_base::_M_arrive)
(__tree_barrier::arrive): Pass address to __atomic_ref constructor.
* testsuite/29_atomics/atomic_ref/ctor.cc: New test.
---
Tested on x86_64-linux. Additionally *atomic_ref* test tested with all
supported standard modes. OK for trunk?
libstdc++-v3/include/bits/atomic_base.h | 11 +-
libstdc++-v3/include/bits/shared_ptr_atomic.h | 16 +-
libstdc++-v3/include/std/atomic | 21 +-
libstdc++-v3/include/std/barrier | 6 +-
.../testsuite/29_atomics/atomic_ref/ctor.cc | 193 ++++++++++++++++++
5 files changed, 228 insertions(+), 19 deletions(-)
create mode 100644 libstdc++-v3/testsuite/29_atomics/atomic_ref/ctor.cc
diff --git a/libstdc++-v3/include/bits/atomic_base.h
b/libstdc++-v3/include/bits/atomic_base.h
index 90b8df55ede..d07cb0f13c8 100644
--- a/libstdc++-v3/include/bits/atomic_base.h
+++ b/libstdc++-v3/include/bits/atomic_base.h
@@ -1561,11 +1561,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__atomic_ref_base& operator=(const __atomic_ref_base&) = delete;
explicit
- __atomic_ref_base(const _Tp& __t)
- : _M_ptr(const_cast<_Tp*>(std::addressof(__t)))
- {
- __glibcxx_assert(((__UINTPTR_TYPE__)_M_ptr % required_alignment) == 0);
- }
+ __atomic_ref_base(const _Tp* __ptr) noexcept
+ : _M_ptr(const_cast<_Tp*>(__ptr))
+ { }
__atomic_ref_base(const __atomic_ref_base&) noexcept = default;
@@ -1607,7 +1605,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
using value_type = typename __atomic_ref_base<const _Tp>::value_type;
explicit
- __atomic_ref_base(_Tp& __t) : __atomic_ref_base<const _Tp>(__t)
+ __atomic_ref_base(_Tp* __ptr) noexcept
+ : __atomic_ref_base<const _Tp>(__ptr)
{ }
value_type
diff --git a/libstdc++-v3/include/bits/shared_ptr_atomic.h
b/libstdc++-v3/include/bits/shared_ptr_atomic.h
index cbc4bf621f4..b16d93e6697 100644
--- a/libstdc++-v3/include/bits/shared_ptr_atomic.h
+++ b/libstdc++-v3/include/bits/shared_ptr_atomic.h
@@ -421,7 +421,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
~_Atomic_count()
{
- auto __val = _AtomicRef(_M_val).load(memory_order_relaxed);
+ auto __val = _AtomicRef(&_M_val).load(memory_order_relaxed);
_GLIBCXX_TSAN_MUTEX_DESTROY(&_M_val);
__glibcxx_assert(!(__val & _S_lock_bit));
if (auto __pi = reinterpret_cast<pointer>(__val))
@@ -443,7 +443,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
{
// To acquire the lock we flip the LSB from 0 to 1.
- _AtomicRef __aref(_M_val);
+ _AtomicRef __aref(&_M_val);
auto __current = __aref.load(memory_order_relaxed);
while (__current & _S_lock_bit)
{
@@ -476,7 +476,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
unlock(memory_order __o) const noexcept
{
_GLIBCXX_TSAN_MUTEX_PRE_UNLOCK(&_M_val);
- _AtomicRef(_M_val).fetch_sub(1, __o);
+ _AtomicRef(&_M_val).fetch_sub(1, __o);
_GLIBCXX_TSAN_MUTEX_POST_UNLOCK(&_M_val);
}
@@ -489,7 +489,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__o = memory_order_release;
auto __x = reinterpret_cast<uintptr_t>(__c._M_pi);
_GLIBCXX_TSAN_MUTEX_PRE_UNLOCK(&_M_val);
- __x = _AtomicRef(_M_val).exchange(__x, __o);
+ __x = _AtomicRef(&_M_val).exchange(__x, __o);
_GLIBCXX_TSAN_MUTEX_POST_UNLOCK(&_M_val);
__c._M_pi = reinterpret_cast<pointer>(__x & ~_S_lock_bit);
}
@@ -502,7 +502,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
auto __old_ptr = __ptr;
_GLIBCXX_TSAN_MUTEX_PRE_UNLOCK(&_M_val);
uintptr_t __old_pi
- = _AtomicRef(_M_val).fetch_sub(1, memory_order_relaxed) - 1u;
+ = _AtomicRef(&_M_val).fetch_sub(1, memory_order_relaxed) - 1u;
_GLIBCXX_TSAN_MUTEX_POST_UNLOCK(&_M_val);
// Ensure that the correct value of _M_ptr is visible after locking,
@@ -528,14 +528,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
// wake up if either of the values changed
return __new_pi != __old_pi || __new_ptr != __old_ptr;
},
- [__o, this] { return _AtomicRef(_M_val).load(__o); });
+ [__o, this] { return _AtomicRef(&_M_val).load(__o); });
}
void
notify_one() noexcept
{
_GLIBCXX_TSAN_MUTEX_PRE_SIGNAL(&_M_val);
- _AtomicRef(_M_val).notify_one();
+ _AtomicRef(&_M_val).notify_one();
_GLIBCXX_TSAN_MUTEX_POST_SIGNAL(&_M_val);
}
@@ -543,7 +543,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
notify_all() noexcept
{
_GLIBCXX_TSAN_MUTEX_PRE_SIGNAL(&_M_val);
- _AtomicRef(_M_val).notify_all();
+ _AtomicRef(&_M_val).notify_all();
_GLIBCXX_TSAN_MUTEX_POST_SIGNAL(&_M_val);
}
#endif
diff --git a/libstdc++-v3/include/std/atomic b/libstdc++-v3/include/std/atomic
index ccb77fa6327..15372ea7994 100644
--- a/libstdc++-v3/include/std/atomic
+++ b/libstdc++-v3/include/std/atomic
@@ -1767,14 +1767,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
struct atomic_ref : __atomic_ref<_Tp>
{
explicit
- atomic_ref(_Tp& __t) noexcept : __atomic_ref<_Tp>(__t)
- { }
+ atomic_ref(_Tp& __t) noexcept
+ : __atomic_ref<_Tp>(std::addressof(__t))
+ {
+ __glibcxx_assert(((__UINTPTR_TYPE__)this->_M_ptr %
this->required_alignment) == 0);
+ }
+
+ // _GLIBCXX_RESOLVE_LIB_DEFECTS
+ // 4472. std::atomic_ref<const T> can be constructed from temporaries
+ explicit
+ atomic_ref(_Tp&&) = delete;
+
+ template<typename _Up>
+ requires is_convertible_v<_Up(*)[1], _Tp(*)[1]>
+ atomic_ref(atomic_ref<_Up> __other) noexcept
+ : __atomic_ref<_Tp>(__other._M_ptr)
+ { }
atomic_ref& operator=(const atomic_ref&) = delete;
atomic_ref(const atomic_ref&) = default;
using __atomic_ref<_Tp>::operator=;
+
+ template<typename>
+ friend struct atomic_ref;
};
#endif // __cpp_lib_atomic_ref
diff --git a/libstdc++-v3/include/std/barrier b/libstdc++-v3/include/std/barrier
index 56270c99e05..978c12a8067 100644
--- a/libstdc++-v3/include/std/barrier
+++ b/libstdc++-v3/include/std/barrier
@@ -146,7 +146,7 @@ It looks different from literature pseudocode for two main
reasons:
if (__current == __end_node)
__current = 0;
auto __expect = __old_phase;
- __atomic_phase_ref_t __phase(__state[__current]
+ __atomic_phase_ref_t __phase(&__state[__current]
.__tickets[__round]);
if (__current == __last_node && (__current_expected & 1))
{
@@ -198,7 +198,7 @@ It looks different from literature pseudocode for two main
reasons:
std::hash<std::thread::id> __hasher;
size_t __current = __hasher(std::this_thread::get_id());
- __atomic_phase_ref_t __phase(_M_phase);
+ __atomic_phase_ref_t __phase(&_M_phase);
const auto __old_phase = __phase.load(memory_order_relaxed);
const auto __cur = static_cast<unsigned char>(__old_phase);
@@ -230,7 +230,7 @@ It looks different from literature pseudocode for two main
reasons:
void
wait(arrival_token&& __old_phase) const
{
- __atomic_phase_const_ref_t __phase(_M_phase);
+ __atomic_phase_const_ref_t __phase(&_M_phase);
__phase.wait(__old_phase, memory_order_acquire);
}
diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/ctor.cc
b/libstdc++-v3/testsuite/29_atomics/atomic_ref/ctor.cc
new file mode 100644
index 00000000000..b027ed853a4
--- /dev/null
+++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/ctor.cc
@@ -0,0 +1,193 @@
+// { dg-do run { target c++26 } }
+// { 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 T>
+void testTemporary()
+{
+ static_assert( !std::is_constructible_v<std::atomic_ref<T>, T> );
+ static_assert( !std::is_constructible_v<std::atomic_ref<const T>, T> );
+ static_assert( !std::is_constructible_v<std::atomic_ref<T>, const T> );
+ static_assert( !std::is_constructible_v<std::atomic_ref<const T>, const T> );
+
+ if constexpr (std::atomic_ref<T>::is_always_lock_free)
+ {
+ static_assert( !std::is_constructible_v<std::atomic_ref<volatile T>, T> );
+ static_assert( !std::is_constructible_v<std::atomic_ref<volatile T>,
volatile T> );
+ static_assert( !std::is_constructible_v<std::atomic_ref<const volatile T>,
T> );
+ static_assert( !std::is_constructible_v<std::atomic_ref<const volatile T>,
const volatile T> );
+ }
+
+ struct Overload
+ {
+ static int operator()(T) { return 1; }
+ static int operator()(std::atomic_ref<T>) { return 2; }
+ };
+ VERIFY( Overload{}(T()) == 1 );
+ VERIFY( Overload{}({T()}) == 1 );
+ static_assert( !requires { Overload{}({{T()}}); } );
+}
+
+template<typename T>
+bool same_address(const std::atomic_ref<T>& t, const std::type_identity_t<T>&
u)
+{
+#if (__cplusplus > 202302L)
+ return t.address() == &u;
+#endif
+ return true;
+}
+
+template<typename From, typename To = From>
+void
+testConv()
+{
+ alignas(std::atomic_ref<From>::required_alignment) From val{};
+ std::atomic_ref<From> src(val);
+ std::atomic_ref<const From> csrc(val);
+
+ std::atomic_ref<const To> d1(src);
+ VERIFY( same_address(d1, val) );
+ std::atomic_ref<const To> d2(csrc);
+ VERIFY( same_address(d2, val) );
+ static_assert( !std::is_convertible_v<std::atomic_ref<const From>,
+ std::atomic_ref<To>> );
+
+ if constexpr (std::atomic_ref<From>::is_always_lock_free)
+ {
+ std::atomic_ref<const volatile To> d4(src);
+ VERIFY( same_address(d4, val) );
+ std::atomic_ref<const volatile To> d5(csrc);
+ VERIFY( same_address(d5, val) );
+ if constexpr (std::is_same_v<From, To>)
+ {
+ std::atomic_ref<volatile To> d3(src);
+ VERIFY( same_address(d3, val) );
+ }
+
+ static_assert( !std::is_convertible_v<std::atomic_ref<volatile From>,
+ std::atomic_ref<To>> );
+ static_assert( !std::is_convertible_v<std::atomic_ref<volatile From>,
+ std::atomic_ref<const To>> );
+ static_assert( !std::is_convertible_v<std::atomic_ref<const volatile From>,
+ std::atomic_ref<To>> );
+ static_assert( !std::is_convertible_v<std::atomic_ref<const volatile From>,
+ std::atomic_ref<const To>> );
+ }
+}
+
+template<typename From, typename To>
+void
+testSimilarConv()
+{
+ testConv<From, To>();
+ static_assert( !std::is_convertible_v< To, From> );
+ static_assert( !std::is_convertible_v< To, const From> );
+ static_assert( !std::is_convertible_v<const To, From> );
+ static_assert( !std::is_convertible_v<const To, const From> );
+
+ if constexpr (std::atomic_ref<From>::is_always_lock_free)
+ {
+ static_assert( !std::is_convertible_v<volatile To, From> );
+ static_assert( !std::is_convertible_v< To, volatile From> );
+ static_assert( !std::is_convertible_v<volatile To, volatile From> );
+
+ static_assert( !std::is_convertible_v<const volatile To,
From> );
+ static_assert( !std::is_convertible_v< To, const volatile
From> );
+ static_assert( !std::is_convertible_v<const volatile To, const volatile
From> );
+
+ static_assert( !std::is_convertible_v< To, volatile From> );
+ static_assert( !std::is_convertible_v<const To, volatile From> );
+ static_assert( !std::is_convertible_v< To, const volatile From> );
+ static_assert( !std::is_convertible_v<const To, const volatile From> );
+
+ static_assert( !std::is_convertible_v<volatile To, From> );
+ static_assert( !std::is_convertible_v<volatile To, const From> );
+ static_assert( !std::is_convertible_v<const volatile To, From> );
+ static_assert( !std::is_convertible_v<const volatile To, const From> );
+ }
+}
+
+template<typename T, template<typename> typename MakePtr = std::add_pointer_t>
+void
+testPtrConv()
+{
+ testConv<MakePtr<T>>();
+ testSimilarConv<MakePtr<T>, MakePtr<const T>>();
+ testSimilarConv<MakePtr<T*>, MakePtr<const T* const>>();
+ testSimilarConv<MakePtr<const T*>, MakePtr<const T* const>>();
+ testSimilarConv<MakePtr<T* const>, MakePtr<const T* const>>();
+
+ testSimilarConv<MakePtr<T[2]>, MakePtr<const T[2]>>();
+ testSimilarConv<MakePtr<T[]>, MakePtr<const T[]>>();
+
+ testSimilarConv<MakePtr<T[2]>, MakePtr<T[]>>();
+ testSimilarConv<MakePtr<T[2]>, MakePtr<const T[]>>();
+ testSimilarConv<MakePtr<const T[2]>, MakePtr<const T[]>>();
+
+ if constexpr (std::atomic_ref<MakePtr<T>>::is_always_lock_free)
+ {
+ testSimilarConv<MakePtr<T>, MakePtr<volatile T>>();
+ testSimilarConv<MakePtr<T>, MakePtr<const volatile T>>();
+ testSimilarConv<MakePtr<volatile T>, MakePtr<const volatile T>>();
+ testSimilarConv<MakePtr<T*>, MakePtr<volatile T* const>>();
+ testSimilarConv<MakePtr<volatile T*>, MakePtr<volatile T* const>>();
+ testSimilarConv<MakePtr<T*>, MakePtr<const volatile T* const>>();
+ testSimilarConv<MakePtr<volatile T*>, MakePtr<const volatile T* const>>();
+ testSimilarConv<MakePtr<volatile T* const>, MakePtr<const volatile T*
const>>();
+
+ testSimilarConv<MakePtr<T[2]>, MakePtr<volatile T[2]>>();
+ testSimilarConv<MakePtr<T[2]>, MakePtr<const volatile T[2]>>();
+ testSimilarConv<MakePtr<const T[2]>, MakePtr<const volatile T[2]>>();
+ testSimilarConv<MakePtr<volatile T[2]>, MakePtr<const volatile T[2]>>();
+
+ testSimilarConv<MakePtr<T[]>, MakePtr<volatile T[]>>();
+ testSimilarConv<MakePtr<T[]>, MakePtr<const volatile T[]>>();
+ testSimilarConv<MakePtr<const T[]>, MakePtr<const volatile T[]>>();
+ testSimilarConv<MakePtr<volatile T[]>, MakePtr<const volatile T[]>>();
+
+ testSimilarConv<MakePtr<T[2]>, MakePtr<volatile T[]>>();
+ testSimilarConv<MakePtr<volatile T[2]>, MakePtr<volatile T[]>>();
+ testSimilarConv<MakePtr<const T[2]>, MakePtr<const volatile T[]>>();
+ testSimilarConv<MakePtr<volatile T[2]>, MakePtr<const volatile T[]>>();
+ testSimilarConv<MakePtr<const volatile T[2]>, MakePtr<const volatile
T[]>>();
+ }
+}
+
+struct D : X {};
+static_assert( !std::is_convertible_v<std::atomic_ref<D>, std::atomic_ref<X>>
);
+static_assert( !std::is_convertible_v<std::atomic_ref<D>,
std::atomic_ref<const X>> );
+static_assert( !std::is_convertible_v<std::atomic_ref<D*>,
std::atomic_ref<const X* const>> );
+static_assert( !std::is_convertible_v<std::atomic_ref<const D*>,
std::atomic_ref<const X* const>> );
+
+template<typename T>
+using member_pointer_t = T X::*;
+
+int
+main()
+{
+ testTemporary<bool>();
+ testTemporary<int>();
+ testTemporary<float>();
+ testTemporary<int*>();
+ testTemporary<X>();
+
+ testConv<bool>();
+ testConv<int>();
+ testConv<float>();
+ testConv<X>();
+ testPtrConv<int>();
+ testPtrConv<int, member_pointer_t>();
+}
--
2.52.0