[libcxx-commits] [libcxx] [libcxx] makes `pair` conditionally trivially copyable for C++20 (PR #84811)

Christopher Di Bella via libcxx-commits libcxx-commits at lists.llvm.org
Mon Mar 11 15:38:37 PDT 2024


https://github.com/cjdb updated https://github.com/llvm/llvm-project/pull/84811

>From b637cc90cd31d0f44bd6a2737ccd4a553015e237 Mon Sep 17 00:00:00 2001
From: Christopher Di Bella <cjdb.ns at gmail.com>
Date: Mon, 11 Mar 2024 18:01:24 +0000
Subject: [PATCH] [libcxx] makes `pair` conditionally trivially copyable for
 C++20

`pair` can have trivial copy and move assignment operators
when both its members have trivial copy and move operators,
which opens `pair` up to being trivially copyable. This is
the first of three possible commits to bring users a more
robust implementation.

It's currently unclear how to implement this without using a
_requires-clause_, so the feature is restricted to C++20 for
now. There has also been some interest in moving this out of
the unstable ABI mode. Both of these changes are intended to
arrive in future pull requests, should their feasibility pan
out.
---
 libcxx/include/__config                       |  3 +
 libcxx/include/__utility/pair.h               | 42 ++++++++++++
 .../pairs/pairs.pair/assign_pair.pass.cpp     | 10 +++
 .../pairs.pair/assign_pair_cxx03.pass.cpp     |  4 +-
 .../pairs/pairs.pair/assign_rv_pair.pass.cpp  | 11 +++
 .../trivially_copyable.compile.pass.cpp       | 68 +++++++++++++++++++
 6 files changed, 137 insertions(+), 1 deletion(-)
 create mode 100644 libcxx/test/std/utilities/utility/pairs/pairs.pair/trivially_copyable.compile.pass.cpp

diff --git a/libcxx/include/__config b/libcxx/include/__config
index 11e13e0c24986a..a8720cd76ca03d 100644
--- a/libcxx/include/__config
+++ b/libcxx/include/__config
@@ -180,6 +180,9 @@
 // requires code not to make these assumptions.
 #    define _LIBCPP_ABI_USE_WRAP_ITER_IN_STD_ARRAY
 #    define _LIBCPP_ABI_USE_WRAP_ITER_IN_STD_STRING_VIEW
+// Allows std::pair's copy assignment and move assignment operators to be trivial
+// when both its element types' respective assignment operators are trivial.
+#    define _LIBCPP_ABI_PAIR_TRIVIALLY_COPYABLE
 #  elif _LIBCPP_ABI_VERSION == 1
 #    if !(defined(_LIBCPP_OBJECT_FORMAT_COFF) || defined(_LIBCPP_OBJECT_FORMAT_XCOFF))
 // Enable compiling copies of now inline methods into the dylib to support
diff --git a/libcxx/include/__utility/pair.h b/libcxx/include/__utility/pair.h
index b488a9829c3849..9af536ccbc1afe 100644
--- a/libcxx/include/__utility/pair.h
+++ b/libcxx/include/__utility/pair.h
@@ -11,6 +11,7 @@
 
 #include <__compare/common_comparison_category.h>
 #include <__compare/synth_three_way.h>
+#include <__concepts/assignable.h>
 #include <__concepts/different_from.h>
 #include <__config>
 #include <__fwd/array.h>
@@ -26,6 +27,7 @@
 #include <__type_traits/common_type.h>
 #include <__type_traits/conditional.h>
 #include <__type_traits/decay.h>
+#include <__type_traits/enable_if.h>
 #include <__type_traits/integral_constant.h>
 #include <__type_traits/is_assignable.h>
 #include <__type_traits/is_constructible.h>
@@ -40,8 +42,11 @@
 #include <__type_traits/is_nothrow_copy_constructible.h>
 #include <__type_traits/is_nothrow_default_constructible.h>
 #include <__type_traits/is_nothrow_move_assignable.h>
+#include <__type_traits/is_object.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_swappable.h>
+#include <__type_traits/is_trivially_copy_assignable.h>
+#include <__type_traits/is_trivially_move_assignable.h>
 #include <__type_traits/nat.h>
 #include <__type_traits/remove_cvref.h>
 #include <__type_traits/unwrap_ref.h>
@@ -67,6 +72,16 @@ struct __non_trivially_copyable_base {
   __non_trivially_copyable_base(__non_trivially_copyable_base const&) _NOEXCEPT {}
 };
 
+#if _LIBCPP_STD_VER >= 20
+template <class _Tp>
+concept __trivially_copy_assignable_object =
+    assignable_from<_Tp&, _Tp const&> && is_trivially_copy_assignable_v<_Tp> && is_object_v<_Tp>;
+
+template <class _Tp>
+concept __trivially_move_assignable_object =
+    assignable_from<_Tp&, _Tp> && is_trivially_move_assignable_v<_Tp> && is_object_v<_Tp>;
+#endif
+
 #if _LIBCPP_STD_VER >= 23
 template <class _Tp>
 struct __is_specialization_of_subrange : false_type {};
@@ -236,6 +251,32 @@ struct _LIBCPP_TEMPLATE_VIS pair
              typename __make_tuple_indices<sizeof...(_Args1)>::type(),
              typename __make_tuple_indices<sizeof...(_Args2) >::type()) {}
 
+#  if _LIBCPP_STD_VER >= 20 && defined(_LIBCPP_ABI_PAIR_TRIVIALLY_COPYABLE)
+  _LIBCPP_HIDE_FROM_ABI pair& operator=(pair const& __p)
+    requires __trivially_copy_assignable_object<first_type> && __trivially_copy_assignable_object<second_type>
+  = default;
+  _LIBCPP_HIDE_FROM_ABI pair& operator=(pair&& __p)
+    requires __trivially_move_assignable_object<first_type> && __trivially_move_assignable_object<second_type>
+  = default;
+
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 pair& operator=(pair const& __p)
+      _NOEXCEPT_(is_nothrow_copy_assignable<first_type>::value&& is_nothrow_copy_assignable<second_type>::value)
+    requires assignable_from<first_type, first_type const&> && assignable_from<second_type, second_type const&>
+  {
+    first  = __p.first;
+    second = __p.second;
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 pair& operator=(pair&& __p)
+      _NOEXCEPT_(is_nothrow_move_assignable<first_type>::value&& is_nothrow_move_assignable<second_type>::value)
+    requires assignable_from<first_type, first_type> && assignable_from<second_type, second_type>
+  {
+    first  = std::forward<first_type>(__p.first);
+    second = std::forward<second_type>(__p.second);
+    return *this;
+  }
+#  else
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 pair&
   operator=(__conditional_t< is_copy_assignable<first_type>::value && is_copy_assignable<second_type>::value,
                              pair,
@@ -254,6 +295,7 @@ struct _LIBCPP_TEMPLATE_VIS pair
     second = std::forward<second_type>(__p.second);
     return *this;
   }
+#  endif
 
   template <
       class _U1,
diff --git a/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_pair.pass.cpp b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_pair.pass.cpp
index 253bd1b85d03ec..f45903f2a263a7 100644
--- a/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_pair.pass.cpp
+++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_pair.pass.cpp
@@ -88,6 +88,16 @@ TEST_CONSTEXPR_CXX20 bool test() {
     assert(&p.second == &inc_obj);
   }
 
+#if TEST_STD_VER >= 20 && defined(_LIBCPP_ABI_PAIR_TRIVIALLY_COPYABLE)
+  static_assert(std::is_trivially_copy_assignable<std::pair<int, int>>::value, "");
+#else
+  static_assert(!std::is_trivially_copy_assignable<std::pair<int, int>>::value, "");
+#endif
+
+  static_assert(!std::is_trivially_copy_assignable<std::pair<CountAssign, int>>::value, "");
+  static_assert(!std::is_trivially_copy_assignable<std::pair<int, CountAssign>>::value, "");
+  static_assert(!std::is_trivially_copy_assignable<std::pair<CountAssign, CountAssign>>::value, "");
+
   return true;
 }
 
diff --git a/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_pair_cxx03.pass.cpp b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_pair_cxx03.pass.cpp
index 2444131b02a0cf..76982c41629ee0 100644
--- a/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_pair_cxx03.pass.cpp
+++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_pair_cxx03.pass.cpp
@@ -62,7 +62,9 @@ int main(int, char**)
       assert(p.second == 'x');
     }
 
-  return 0;
+    static_assert(!std::is_trivially_copy_assignable<std::pair<int, int> >::value, "");
+
+    return 0;
 }
 
 struct Incomplete {};
diff --git a/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair.pass.cpp b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair.pass.cpp
index 8e5d9c39ae8868..a4c178db25492a 100644
--- a/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair.pass.cpp
+++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair.pass.cpp
@@ -130,6 +130,17 @@ TEST_CONSTEXPR_CXX20 bool test() {
     static_assert(!std::is_move_assignable<P5>::value, "");
     static_assert(!std::is_move_assignable<P6>::value, "");
   }
+
+#if TEST_STD_VER >= 20 && defined(_LIBCPP_ABI_PAIR_TRIVIALLY_COPYABLE)
+  static_assert(std::is_trivially_move_assignable<std::pair<int, int>>::value, "");
+#else
+  static_assert(!std::is_trivially_move_assignable<std::pair<int, int>>::value, "");
+#endif
+
+  static_assert(!std::is_trivially_move_assignable<std::pair<CountAssign, int>>::value, "");
+  static_assert(!std::is_trivially_move_assignable<std::pair<int, CountAssign>>::value, "");
+  static_assert(!std::is_trivially_move_assignable<std::pair<CountAssign, CountAssign>>::value, "");
+
   return true;
 }
 
diff --git a/libcxx/test/std/utilities/utility/pairs/pairs.pair/trivially_copyable.compile.pass.cpp b/libcxx/test/std/utilities/utility/pairs/pairs.pair/trivially_copyable.compile.pass.cpp
new file mode 100644
index 00000000000000..19d6f7b46316c4
--- /dev/null
+++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/trivially_copyable.compile.pass.cpp
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// <utility>
+
+// template <class T1, class T2> struct pair
+
+// Checks that `std::pair` is actually trivially copyable.
+
+#include <type_traits>
+#include <utility>
+
+#if defined(_LIBCPP_ABI_PAIR_TRIVIALLY_COPYABLE)
+static_assert(std::is_trivially_copyable_v<std::pair<int, int>>);
+static_assert(std::is_trivially_copy_constructible_v<std::pair<int, int>>);
+static_assert(std::is_trivially_move_constructible_v<std::pair<int, int>>);
+static_assert(std::is_trivially_copy_assignable_v<std::pair<int, int>>);
+static_assert(std::is_trivially_move_assignable_v<std::pair<int, int>>);
+static_assert(std::is_trivially_destructible_v<std::pair<int, int>>);
+
+// static_assert(std::is_trivially_copyable_v<std::pair<int, int const>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<int const, int>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<int const, int const>>);
+
+// static_assert(!std::is_trivially_copyable_v<std::pair<int, int&>>);
+// static_assert(!std::is_trivially_copyable_v<std::pair<int&, int>>);
+// static_assert(!std::is_trivially_copyable_v<std::pair<int&, int&>>);
+
+// enum E {};
+// static_assert(std::is_trivially_copyable_v<std::pair<E, E>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<E, E const>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<E const, E>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<E const, E const>>);
+
+// static_assert(std::is_trivially_copyable_v<std::pair<E, int>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<E, int const>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<E const, int>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<E const, int const>>);
+
+// struct S {};
+// static_assert(std::is_trivially_copyable_v<std::pair<S, S>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<S, S const>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<S const, S>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<S const, S const>>);
+
+// static_assert(std::is_trivially_copyable_v<std::pair<S, int>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<S, int const>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<S const, int>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<S const, int const>>);
+
+// static_assert(std::is_trivially_copyable_v<std::pair<S, E>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<S, E const>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<S const, E>>);
+// static_assert(std::is_trivially_copyable_v<std::pair<S const, E const>>);
+
+// struct S2 {
+// 	S2(S2&&) = delete;
+// };
+// static_assert(std::is_trivially_copyable_v<S2>);
+
+#endif



More information about the libcxx-commits mailing list