Hello,

The attached patch modifies std::atomic's primary template. The goal is to improve compatibility with Clang, while also possibly making it more complaint with the changes introduced by P0883 / C++20.

Simplifying, std::atomic has a `T t = T()` NDSMI and a defaulted default constructor. The crux of the problem is that Clang seems to be stricter than GCC when that constructor is considered / instantiated.

Given a non-default constructible type NDC, doing something like

constexpr bool b = std::is_default_constructible_v<std::atomic<NDC>>;

causes a hard error on Clang because it will "see" the call to `NDC()` in the NDSMI. The code is instead accepted by GCC. This hard error will happen anywhere one "mentions" std::atomic<NDC>'s default constructor, for instance in libstdc++'s C++20 std::pair implementation (uses them in the explicit(bool) bits). You can play with this here:

https://gcc.godbolt.org/z/xcr4zK8hx

PR116769 argues that Clang's behavior is the correct one here, so this patch improves compat with Clang by removing the defaulted default constructor.

A related issue is: what's the value of `b` above? std::atomic's default constructor is not constrained, so it should be `true`. Right now we're reporting `false` instead.


Thoughts?


Thank you,
--
Giuseppe D'Angelo
From 274543f82ac4e77aaf9c8f5158a44b98538537e5 Mon Sep 17 00:00:00 2001
From: Giuseppe D'Angelo <giuseppe.dang...@kdab.com>
Date: Sat, 21 Sep 2024 10:36:20 +0200
Subject: [PATCH] libstdc++: improve std::atomic compatibility with Clang

std::atomic<T>'s primary template in libstdc++ has a defaulted default
constructor. Value-initialization of the T member (since C++20 /
P0883R2) is done via a NSDMI (= T()).

There is implementation divergence on the behavior of the defaulted
default constructor in case T isn't default constructible. In particular
GCC seems to effectively treat the default constructor as deleted /
constrained away; whereas Clang triggers a hard error due to the NDSMI.

It seems that GCC's behavior is a non-standard extension here, see the
discussion in PR116769; the presence of a NDSMI shouldn't make the
constructor deleted ([class.default.ctor]/2.5 does not apply) and the
interpretation of [class.default.ctor]/4 seems to match Clang's behavior
instead.

Therefore, this commit removes std::atomic's defaulted default
constructor and changes it to a non-defaulted one; value-initialization
of the data member is moved from the NDSMI to the member init list.
The new signature matches the one in the Standard as per
[atomics.types.operations]/1.

Finally: std::atomic<T>'s default constructor is not constrained on T
being default constructible; it instead Mandates that condition. I'm
not sure why that's the case, but this requires removing a testcase
where there's an explicit class instantiation of std::atomic with a
non-default constructible type. Such an instantiation would instantiate
all member functions (as per [temp.explicit]/9), including the default
constructor, which now triggers an hard error.

In its place I've instead added a static_assert checking that
std::atomic is default constructible even with a non-default
constructible T.

libstdc++-v3/ChangeLog:

	* include/std/atomic: Turn the defaulted default constructor
	in a non-defaulted one; remove the NSDMI for the _M_i member.
	Drop the _GLIBCXX20_INIT macro as it is not needed any more.
	* testsuite/29_atomics/atomic/69301.cc: Remove the explicit
	class instantation, which would cause a hard error. Add a new
	test.

Signed-off-by: Giuseppe D'Angelo <giuseppe.dang...@kdab.com>
---
 libstdc++-v3/include/std/atomic                | 18 +++++++-----------
 .../testsuite/29_atomics/atomic/69301.cc       |  6 +++++-
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/libstdc++-v3/include/std/atomic b/libstdc++-v3/include/std/atomic
index 36ff89a146c..85b25ec4e8d 100644
--- a/libstdc++-v3/include/std/atomic
+++ b/libstdc++-v3/include/std/atomic
@@ -187,14 +187,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif // __cpp_lib_atomic_wait
   };
 
-/// @cond undocumented
-#if __cpp_lib_atomic_value_initialization
-# define _GLIBCXX20_INIT(I) = I
-#else
-# define _GLIBCXX20_INIT(I)
-#endif
-/// @endcond
-
   /**
    *  @brief Generic atomic type, primary class template.
    *
@@ -214,7 +206,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       static constexpr int _S_alignment
         = _S_min_alignment > alignof(_Tp) ? _S_min_alignment : alignof(_Tp);
 
-      alignas(_S_alignment) _Tp _M_i _GLIBCXX20_INIT(_Tp());
+      alignas(_S_alignment) _Tp _M_i;
 
       static_assert(__is_trivially_copyable(_Tp),
 		    "std::atomic requires a trivially copyable type");
@@ -230,7 +222,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
 
     public:
-      atomic() = default;
+      constexpr atomic() noexcept(__is_nothrow_constructible(_Tp))
+#if __cpp_lib_atomic_value_initialization
+	: _M_i()
+#endif
+      {}
+
       ~atomic() noexcept = default;
       atomic(const atomic&) = delete;
       atomic& operator=(const atomic&) = delete;
@@ -412,7 +409,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif // __cpp_lib_atomic_wait
 
     };
-#undef _GLIBCXX20_INIT
 
   /// Partial specialization for pointer types.
   template<typename _Tp>
diff --git a/libstdc++-v3/testsuite/29_atomics/atomic/69301.cc b/libstdc++-v3/testsuite/29_atomics/atomic/69301.cc
index 875e3344a2c..1007c2a4da5 100644
--- a/libstdc++-v3/testsuite/29_atomics/atomic/69301.cc
+++ b/libstdc++-v3/testsuite/29_atomics/atomic/69301.cc
@@ -27,7 +27,11 @@ struct NonDefaultConstructible
   int val;
 };
 
-template class std::atomic<NonDefaultConstructible>;
+static_assert(
+  std::is_default_constructible<std::atomic<NonDefaultConstructible>>::value,
+  "std::atomic should always default constructible as its default "
+  "constructor is not constrained"
+);
 
 void
 test01()
-- 
2.34.1

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature

Reply via email to