[libcxx-commits] [libcxx] [libc++] Implement LWG2991: variant copy constructor missing noexcept(see below) (PR #230241)

via libcxx-commits libcxx-commits at lists.llvm.org
Fri Oct 9 04:23:05 PDT 2026


https://github.com/KaroliShp updated https://github.com/llvm/llvm-project/pull/230241

>From 74fc538e4de750fbea458bcf02e46eb36f8f210b Mon Sep 17 00:00:00 2001
From: Karolis <karolis.spukas at gmail.com>
Date: Thu, 8 Oct 2026 19:03:50 +0100
Subject: [PATCH 1/2] [libc++] Implement LWG2991: variant copy constructor
 missing noexcept(see below)

---
 libcxx/docs/Status/Cxx26Issues.csv            |  2 +-
 libcxx/include/variant                        |  8 ++---
 .../variant.ctor/copy.pass.cpp                | 35 ++++++++++++++++++-
 3 files changed, 39 insertions(+), 6 deletions(-)

diff --git a/libcxx/docs/Status/Cxx26Issues.csv b/libcxx/docs/Status/Cxx26Issues.csv
index 7cb257a49c30f0..7a0be30b70986f 100644
--- a/libcxx/docs/Status/Cxx26Issues.csv
+++ b/libcxx/docs/Status/Cxx26Issues.csv
@@ -146,7 +146,7 @@
 "`LWG4245 <https://wg21.link/LWG4245>`__","Operators that interact with ``counted_iterator`` and ``default_sentinel_t`` should be ``noexcept``","2025-06 (Sofia)","","","`#148226 <https://github.com/llvm/llvm-project/issues/148226>`__",""
 "`LWG4247 <https://wg21.link/LWG4247>`__","Header ``<stdbit.h>`` is not yet freestanding","2025-06 (Sofia)","","","`#148227 <https://github.com/llvm/llvm-project/issues/148227>`__",""
 "","","","","","",""
-"`LWG2991 <https://wg21.link/LWG2991>`__","``variant copy`` constructor missing ``noexcept(see below)``","2025-11 (Kona)","","","`#171301 <https://github.com/llvm/llvm-project/issues/171301>`__",""
+"`LWG2991 <https://wg21.link/LWG2991>`__","``variant copy`` constructor missing ``noexcept(see below)``","2025-11 (Kona)","|Complete|","24","`#171301 <https://github.com/llvm/llvm-project/issues/171301>`__",""
 "`LWG3090 <https://wg21.link/LWG3090>`__","What is ยง[time.duration.cons]p4's ""no overflow is induced in the conversion"" intended to mean?","2025-11 (Kona)","","","`#171302 <https://github.com/llvm/llvm-project/issues/171302>`__",""
 "`LWG3343 <https://wg21.link/LWG3343>`__","Ordering of calls to ``unlock()`` and ``notify_all()`` in Effects element of ``notify_all_at_thread_exit()`` should be reversed","2025-11 (Kona)","|Complete|","16","`#105356 <https://github.com/llvm/llvm-project/issues/105356>`__",""
 "`LWG3454 <https://wg21.link/LWG3454>`__","``pointer_traits::pointer_to`` should be ``constexpr``","2025-11 (Kona)","|Complete|","16","`#171307 <https://github.com/llvm/llvm-project/issues/171307>`__",""
diff --git a/libcxx/include/variant b/libcxx/include/variant
index 393aea461af9f7..765035db8117a4 100644
--- a/libcxx/include/variant
+++ b/libcxx/include/variant
@@ -22,7 +22,7 @@ namespace std {
 
     // 20.7.2.1, constructors
     constexpr variant() noexcept(see below);
-    constexpr variant(const variant&);
+    constexpr variant(const variant&) noexcept(see below);
     constexpr variant(variant&&) noexcept(see below);
 
     template <class T> constexpr variant(T&&) noexcept(see below);
@@ -904,9 +904,9 @@ _LIBCPP_VARIANT_COPY_CONSTRUCTOR(
 
 _LIBCPP_VARIANT_COPY_CONSTRUCTOR(
     _Trait::_Available,
-    _LIBCPP_HIDE_FROM_ABI
-    _LIBCPP_CONSTEXPR_SINCE_CXX20 __copy_constructor(const __copy_constructor& __that) : __copy_constructor(
-        __valueless_t{}) { this->__generic_construct(*this, __that); } _LIBCPP_EAT_SEMICOLON);
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __copy_constructor(const __copy_constructor& __that) noexcept(
+        __all<is_nothrow_copy_constructible_v<_Types>...>::value)
+    : __copy_constructor(__valueless_t{}) { this->__generic_construct(*this, __that); } _LIBCPP_EAT_SEMICOLON);
 
 _LIBCPP_VARIANT_COPY_CONSTRUCTOR(
     _Trait::_Unavailable,
diff --git a/libcxx/test/std/utilities/variant/variant.variant/variant.ctor/copy.pass.cpp b/libcxx/test/std/utilities/variant/variant.variant/variant.ctor/copy.pass.cpp
index 820ff9e0d1a9d0..8a422a40f70936 100644
--- a/libcxx/test/std/utilities/variant/variant.variant/variant.ctor/copy.pass.cpp
+++ b/libcxx/test/std/utilities/variant/variant.variant/variant.ctor/copy.pass.cpp
@@ -12,7 +12,7 @@
 
 // template <class ...Types> class variant;
 
-// constexpr variant(variant const&);
+// constexpr variant(variant const&) noexcept(see below);
 
 #include <cassert>
 #include <type_traits>
@@ -52,6 +52,16 @@ struct NTCopy {
 static_assert(!std::is_trivially_copy_constructible<NTCopy>::value, "");
 static_assert(std::is_copy_constructible<NTCopy>::value, "");
 
+struct NoThrowNTCopy {
+  constexpr NoThrowNTCopy(int v) : value(v) {}
+  NoThrowNTCopy(const NoThrowNTCopy& that) noexcept : value(that.value) {}
+  NoThrowNTCopy(NoThrowNTCopy&&) = delete;
+  int value;
+};
+
+static_assert(!std::is_trivially_copy_constructible<NoThrowNTCopy>::value, "");
+static_assert(std::is_copy_constructible<NoThrowNTCopy>::value, "");
+
 struct TCopy {
   constexpr TCopy(int v) : value(v) {}
   TCopy(TCopy const&) = default;
@@ -99,6 +109,28 @@ void makeEmpty(Variant& v) {
 }
 #endif // TEST_HAS_NO_EXCEPTIONS
 
+constexpr void test_copy_noexcept() {
+  {
+    using V = std::variant<int, long>;
+    static_assert(std::is_nothrow_copy_constructible<V>::value, "");
+  }
+  {
+    using V = std::variant<int, TCopy>;
+    static_assert(std::is_nothrow_copy_constructible<TCopy>::value, "");
+    static_assert(std::is_nothrow_copy_constructible<V>::value, "");
+  }
+  {
+    using V = std::variant<int, NTCopy>;
+    static_assert(!std::is_nothrow_copy_constructible<NTCopy>::value, "");
+    static_assert(!std::is_nothrow_copy_constructible<V>::value, "");
+  }
+  {
+    using V = std::variant<int, NoThrowNTCopy>;
+    static_assert(std::is_nothrow_copy_constructible<NoThrowNTCopy>::value, "");
+    static_assert(std::is_nothrow_copy_constructible<V>::value, "");
+  }
+}
+
 constexpr void test_copy_ctor_sfinae() {
   {
     using V = std::variant<int, long>;
@@ -268,6 +300,7 @@ TEST_CONSTEXPR_CXX20 void test_constexpr_copy_ctor_non_trivial() {
 void non_constexpr_test() { test_copy_ctor_valueless_by_exception(); }
 
 constexpr bool cxx17_constexpr_test() {
+  test_copy_noexcept();
   test_copy_ctor_sfinae();
   test_constexpr_copy_ctor_trivial();
 

>From 75947ed91219b65345d2042260dbed0614d73315 Mon Sep 17 00:00:00 2001
From: Karolis <karolis.spukas at gmail.com>
Date: Fri, 9 Oct 2026 12:22:50 +0100
Subject: [PATCH 2/2] Address review comments

---
 .../variant.ctor/copy.pass.cpp                | 40 +++++--------------
 1 file changed, 11 insertions(+), 29 deletions(-)

diff --git a/libcxx/test/std/utilities/variant/variant.variant/variant.ctor/copy.pass.cpp b/libcxx/test/std/utilities/variant/variant.variant/variant.ctor/copy.pass.cpp
index 8a422a40f70936..55641593571b1e 100644
--- a/libcxx/test/std/utilities/variant/variant.variant/variant.ctor/copy.pass.cpp
+++ b/libcxx/test/std/utilities/variant/variant.variant/variant.ctor/copy.pass.cpp
@@ -51,16 +51,15 @@ struct NTCopy {
 
 static_assert(!std::is_trivially_copy_constructible<NTCopy>::value, "");
 static_assert(std::is_copy_constructible<NTCopy>::value, "");
+static_assert(!std::is_nothrow_copy_constructible_v<NTCopy>);
 
 struct NoThrowNTCopy {
-  constexpr NoThrowNTCopy(int v) : value(v) {}
-  NoThrowNTCopy(const NoThrowNTCopy& that) noexcept : value(that.value) {}
-  NoThrowNTCopy(NoThrowNTCopy&&) = delete;
-  int value;
+  NoThrowNTCopy(const NoThrowNTCopy&) noexcept;
 };
 
-static_assert(!std::is_trivially_copy_constructible<NoThrowNTCopy>::value, "");
-static_assert(std::is_copy_constructible<NoThrowNTCopy>::value, "");
+static_assert(!std::is_trivially_copy_constructible_v<NoThrowNTCopy>);
+static_assert(std::is_copy_constructible_v<NoThrowNTCopy>);
+static_assert(std::is_nothrow_copy_constructible_v<NoThrowNTCopy>);
 
 struct TCopy {
   constexpr TCopy(int v) : value(v) {}
@@ -70,6 +69,7 @@ struct TCopy {
 };
 
 static_assert(std::is_trivially_copy_constructible<TCopy>::value, "");
+static_assert(std::is_nothrow_copy_constructible_v<TCopy>);
 
 struct TCopyNTMove {
   constexpr TCopyNTMove(int v) : value(v) {}
@@ -80,6 +80,11 @@ struct TCopyNTMove {
 
 static_assert(std::is_trivially_copy_constructible<TCopyNTMove>::value, "");
 
+static_assert(std::is_nothrow_copy_constructible_v<std::variant<int, long>>);
+static_assert(std::is_nothrow_copy_constructible_v<std::variant<int, TCopy>>);
+static_assert(!std::is_nothrow_copy_constructible_v<std::variant<int, NTCopy>>);
+static_assert(std::is_nothrow_copy_constructible_v<std::variant<int, NoThrowNTCopy>>);
+
 #ifndef TEST_HAS_NO_EXCEPTIONS
 struct MakeEmptyT {
   static int alive;
@@ -109,28 +114,6 @@ void makeEmpty(Variant& v) {
 }
 #endif // TEST_HAS_NO_EXCEPTIONS
 
-constexpr void test_copy_noexcept() {
-  {
-    using V = std::variant<int, long>;
-    static_assert(std::is_nothrow_copy_constructible<V>::value, "");
-  }
-  {
-    using V = std::variant<int, TCopy>;
-    static_assert(std::is_nothrow_copy_constructible<TCopy>::value, "");
-    static_assert(std::is_nothrow_copy_constructible<V>::value, "");
-  }
-  {
-    using V = std::variant<int, NTCopy>;
-    static_assert(!std::is_nothrow_copy_constructible<NTCopy>::value, "");
-    static_assert(!std::is_nothrow_copy_constructible<V>::value, "");
-  }
-  {
-    using V = std::variant<int, NoThrowNTCopy>;
-    static_assert(std::is_nothrow_copy_constructible<NoThrowNTCopy>::value, "");
-    static_assert(std::is_nothrow_copy_constructible<V>::value, "");
-  }
-}
-
 constexpr void test_copy_ctor_sfinae() {
   {
     using V = std::variant<int, long>;
@@ -300,7 +283,6 @@ TEST_CONSTEXPR_CXX20 void test_constexpr_copy_ctor_non_trivial() {
 void non_constexpr_test() { test_copy_ctor_valueless_by_exception(); }
 
 constexpr bool cxx17_constexpr_test() {
-  test_copy_noexcept();
   test_copy_ctor_sfinae();
   test_constexpr_copy_ctor_trivial();
 



More information about the libcxx-commits mailing list