[libcxx-commits] [libcxx] 9e35fc0 - [libc++] Clean up pair's constructors and assignment operators

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Fri Apr 28 12:16:52 PDT 2023


Author: Louis Dionne
Date: 2023-04-28T15:16:15-04:00
New Revision: 9e35fc07f4001c5803069013bcfc266ad99da637

URL: https://github.com/llvm/llvm-project/commit/9e35fc07f4001c5803069013bcfc266ad99da637
DIFF: https://github.com/llvm/llvm-project/commit/9e35fc07f4001c5803069013bcfc266ad99da637.diff

LOG: [libc++] Clean up pair's constructors and assignment operators

This patch makes std::pair's constructors and assignment operators
closer to conforming in C++23. The only missing bit I am aware of
now is `reference_constructs_from_temporary_v` checks, which we
don't have the tools for yet.

This patch also refactors a long-standing non-standard extension where
we'd provide constructors for tuple-like types in all standard modes. The
criteria for being a tuple-like type are different from pair-like types
as introduced recently in the standard, leading to a lot of complexity
when trying to implement recent papers that touch the pair constructors.

After this patch, the pre-C++23 extension is provided in a self-contained
block so that we can easily deprecate and eventually remove the extension
in future releases.

Differential Revision: https://reviews.llvm.org/D143914

Added: 
    libcxx/test/std/utilities/utility/pairs/pairs.pair/assign.pair_like_rv.pass.cpp
    libcxx/test/std/utilities/utility/pairs/pairs.pair/assign.pair_like_rv_const.pass.cpp
    libcxx/test/std/utilities/utility/pairs/pairs.pair/ctor.pair_like.pass.cpp

Modified: 
    libcxx/include/__tuple/sfinae_helpers.h
    libcxx/include/__utility/pair.h
    libcxx/include/utility

Removed: 
    


################################################################################
diff  --git a/libcxx/include/__tuple/sfinae_helpers.h b/libcxx/include/__tuple/sfinae_helpers.h
index f00fad5010089..384e3ca5aaa66 100644
--- a/libcxx/include/__tuple/sfinae_helpers.h
+++ b/libcxx/include/__tuple/sfinae_helpers.h
@@ -108,19 +108,6 @@ struct _LIBCPP_TEMPLATE_VIS tuple_element<_Ip, tuple<_Tp...> >
     typedef _LIBCPP_NODEBUG typename tuple_element<_Ip, __tuple_types<_Tp...> >::type type;
 };
 
-template <bool _IsTuple, class _SizeTrait, size_t _Expected>
-struct __tuple_like_with_size_imp : false_type {};
-
-template <class _SizeTrait, size_t _Expected>
-struct __tuple_like_with_size_imp<true, _SizeTrait, _Expected>
-    : integral_constant<bool, _SizeTrait::value == _Expected> {};
-
-template <class _Tuple, size_t _ExpectedSize, class _RawTuple = __libcpp_remove_reference_t<_Tuple> >
-using __tuple_like_with_size _LIBCPP_NODEBUG = __tuple_like_with_size_imp<
-                                   __tuple_like_ext<_RawTuple>::value,
-                                   tuple_size<_RawTuple>, _ExpectedSize
-                              >;
-
 struct _LIBCPP_TYPE_VIS __check_tuple_constructor_fail {
 
     static _LIBCPP_HIDE_FROM_ABI constexpr bool __enable_explicit_default() { return false; }

diff  --git a/libcxx/include/__utility/pair.h b/libcxx/include/__utility/pair.h
index 3f803687e5954..00f13e462c6a6 100644
--- a/libcxx/include/__utility/pair.h
+++ b/libcxx/include/__utility/pair.h
@@ -11,10 +11,13 @@
 
 #include <__compare/common_comparison_category.h>
 #include <__compare/synth_three_way.h>
+#include <__concepts/
diff erent_from.h>
 #include <__config>
 #include <__functional/unwrap_ref.h>
 #include <__fwd/get.h>
+#include <__fwd/subrange.h>
 #include <__fwd/tuple.h>
+#include <__tuple/pair_like.h>
 #include <__tuple/sfinae_helpers.h>
 #include <__tuple/tuple_element.h>
 #include <__tuple/tuple_indices.h>
@@ -23,6 +26,7 @@
 #include <__type_traits/common_type.h>
 #include <__type_traits/conditional.h>
 #include <__type_traits/decay.h>
+#include <__type_traits/integral_constant.h>
 #include <__type_traits/is_assignable.h>
 #include <__type_traits/is_constructible.h>
 #include <__type_traits/is_convertible.h>
@@ -39,6 +43,8 @@
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_swappable.h>
 #include <__type_traits/nat.h>
+#include <__type_traits/remove_cvref.h>
+#include <__utility/declval.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
 #include <__utility/piecewise_construct.h>
@@ -58,6 +64,14 @@ struct __non_trivially_copyable_base {
   __non_trivially_copyable_base(__non_trivially_copyable_base const&) _NOEXCEPT {}
 };
 
+#if _LIBCPP_STD_VER >= 23
+template <class _Tp>
+struct __is_specialization_of_subrange : false_type {};
+
+template <class _Iter, class _Sent, ranges::subrange_kind _Kind>
+struct __is_specialization_of_subrange<ranges::subrange<_Iter, _Sent, _Kind>> : true_type {};
+#endif
+
 template <class _T1, class _T2>
 struct _LIBCPP_TEMPLATE_VIS pair
 #if defined(_LIBCPP_DEPRECATED_ABI_DISABLE_PAIR_TRIVIAL_COPY_CTOR)
@@ -132,32 +146,6 @@ struct _LIBCPP_TEMPLATE_VIS pair
     using _CheckArgsDep _LIBCPP_NODEBUG = typename conditional<
       _MaybeEnable, _CheckArgs, __check_tuple_constructor_fail>::type;
 
-    struct _CheckTupleLikeConstructor {
-        template <class _Tuple>
-        static _LIBCPP_HIDE_FROM_ABI constexpr bool __enable_implicit() {
-            return __tuple_convertible<_Tuple, pair>::value;
-        }
-
-        template <class _Tuple>
-        static _LIBCPP_HIDE_FROM_ABI constexpr bool __enable_explicit() {
-            return __tuple_constructible<_Tuple, pair>::value
-               && !__tuple_convertible<_Tuple, pair>::value;
-        }
-
-        template <class _Tuple>
-        static _LIBCPP_HIDE_FROM_ABI constexpr bool __enable_assign() {
-            return __tuple_assignable<_Tuple, pair>::value;
-        }
-    };
-
-    template <class _Tuple>
-    using _CheckTLC _LIBCPP_NODEBUG = __conditional_t<
-        __tuple_like_with_size<_Tuple, 2>::value
-            && !is_same<__decay_t<_Tuple>, pair>::value,
-        _CheckTupleLikeConstructor,
-        __check_tuple_constructor_fail
-    >;
-
     template<bool _Dummy = true, typename enable_if<
             _CheckArgsDep<_Dummy>::__enable_explicit_default()
     >::type* = nullptr>
@@ -279,21 +267,24 @@ struct _LIBCPP_TEMPLATE_VIS pair
         : first(std::move(__p.first)), second(std::move(__p.second)) {}
 #endif
 
-    template<class _Tuple, typename enable_if<
-            _CheckTLC<_Tuple>::template __enable_explicit<_Tuple>()
-    >::type* = nullptr>
-    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
-    explicit pair(_Tuple&& __p)
-        : first(std::get<0>(std::forward<_Tuple>(__p))),
-          second(std::get<1>(std::forward<_Tuple>(__p))) {}
+#  if _LIBCPP_STD_VER >= 23
+    // This is a workaround for http://llvm.org/PR60710. We should be able to remove it once Clang is fixed.
+    template <class _PairLike, bool _Enable = tuple_size<remove_cvref_t<_PairLike>>::value == 2>
+    _LIBCPP_HIDE_FROM_ABI static constexpr bool __pair_like_explicit_wknd() {
+        if constexpr (tuple_size<remove_cvref_t<_PairLike>>::value == 2) {
+            return !is_convertible_v<decltype(std::get<0>(std::declval<_PairLike&&>())), first_type> ||
+                   !is_convertible_v<decltype(std::get<1>(std::declval<_PairLike&&>())), second_type>;
+        }
+        return false;
+    }
 
-    template<class _Tuple, typename enable_if<
-            _CheckTLC<_Tuple>::template __enable_implicit<_Tuple>()
-    >::type* = nullptr>
-    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
-    pair(_Tuple&& __p)
-        : first(std::get<0>(std::forward<_Tuple>(__p))),
-          second(std::get<1>(std::forward<_Tuple>(__p))) {}
+    template <__pair_like _PairLike>
+      requires(is_constructible_v<first_type, decltype(std::get<0>(std::declval<_PairLike&&>()))> &&
+               is_constructible_v<second_type, decltype(std::get<1>(std::declval<_PairLike&&>()))>)
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit(__pair_like_explicit_wknd<_PairLike>())
+        pair(_PairLike&& __p)
+        : first(std::get<0>(std::forward<_PairLike>(__p))), second(std::get<1>(std::forward<_PairLike>(__p))) {}
+#  endif
 
     template <class... _Args1, class... _Args2>
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
@@ -331,7 +322,29 @@ struct _LIBCPP_TEMPLATE_VIS pair
         return *this;
     }
 
-#if _LIBCPP_STD_VER >= 23
+    template <class _U1, class _U2, __enable_if_t<
+        is_assignable<first_type&, _U1 const&>::value &&
+        is_assignable<second_type&, _U2 const&>::value
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
+    pair& operator=(pair<_U1, _U2> const& __p) {
+        first = __p.first;
+        second = __p.second;
+        return *this;
+    }
+
+    template <class _U1, class _U2, __enable_if_t<
+        is_assignable<first_type&, _U1>::value &&
+        is_assignable<second_type&, _U2>::value
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
+    pair& operator=(pair<_U1, _U2>&& __p) {
+        first = std::forward<_U1>(__p.first);
+        second = std::forward<_U2>(__p.second);
+        return *this;
+    }
+
+#  if _LIBCPP_STD_VER >= 23
     _LIBCPP_HIDE_FROM_ABI constexpr
     const pair& operator=(pair const& __p) const
       noexcept(is_nothrow_copy_assignable_v<const first_type> &&
@@ -373,17 +386,165 @@ struct _LIBCPP_TEMPLATE_VIS pair
         second = std::forward<_U2>(__p.second);
         return *this;
     }
-#endif // _LIBCPP_STD_VER >= 23
 
-    template <class _Tuple, typename enable_if<
-            _CheckTLC<_Tuple>::template __enable_assign<_Tuple>()
-     >::type* = nullptr>
-    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
-    pair& operator=(_Tuple&& __p) {
-        first = std::get<0>(std::forward<_Tuple>(__p));
-        second = std::get<1>(std::forward<_Tuple>(__p));
+    template <__pair_like _PairLike>
+      requires(__
diff erent_from<_PairLike, pair> &&
+               !__is_specialization_of_subrange<remove_cvref_t<_PairLike>>::value &&
+               is_assignable_v<first_type&, decltype(std::get<0>(std::declval<_PairLike>()))> &&
+               is_assignable_v<second_type&, decltype(std::get<1>(std::declval<_PairLike>()))>)
+    _LIBCPP_HIDE_FROM_ABI constexpr pair& operator=(_PairLike&& __p) {
+        first  = std::get<0>(std::forward<_PairLike>(__p));
+        second = std::get<1>(std::forward<_PairLike>(__p));
+        return *this;
+    }
+
+    template <__pair_like _PairLike>
+      requires(__
diff erent_from<_PairLike, pair> &&
+               !__is_specialization_of_subrange<remove_cvref_t<_PairLike>>::value &&
+               is_assignable_v<first_type const&, decltype(std::get<0>(std::declval<_PairLike>()))> &&
+               is_assignable_v<second_type const&, decltype(std::get<1>(std::declval<_PairLike>()))>)
+    _LIBCPP_HIDE_FROM_ABI constexpr pair const& operator=(_PairLike&& __p) const {
+        first  = std::get<0>(std::forward<_PairLike>(__p));
+        second = std::get<1>(std::forward<_PairLike>(__p));
+        return *this;
+    }
+#  endif // _LIBCPP_STD_VER >= 23
+
+    // Prior to C++23, we provide an approximation of constructors and assignment operators from
+    // pair-like types. This was historically provided as an extension.
+#if _LIBCPP_STD_VER < 23
+    // from std::tuple
+    template<class _U1, class _U2, __enable_if_t<
+        is_convertible<_U1 const&, _T1>::value &&
+        is_convertible<_U2 const&, _T2>::value
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
+    pair(tuple<_U1, _U2> const& __p)
+        : first(std::get<0>(__p)),
+          second(std::get<1>(__p)) {}
+
+    template<class _U1, class _U2, __enable_if_t<
+        is_constructible<_T1, _U1 const&>::value &&
+        is_constructible<_T2, _U2 const&>::value &&
+        !(is_convertible<_U1 const&, _T1>::value &&
+          is_convertible<_U2 const&, _T2>::value)
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
+    explicit
+    pair(tuple<_U1, _U2> const& __p)
+        : first(std::get<0>(__p)),
+          second(std::get<1>(__p)) {}
+
+    template<class _U1, class _U2, __enable_if_t<
+        is_convertible<_U1, _T1>::value &&
+        is_convertible<_U2, _T2>::value
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
+    pair(tuple<_U1, _U2>&& __p)
+        : first(std::get<0>(std::move(__p))),
+          second(std::get<1>(std::move(__p))) {}
+
+    template<class _U1, class _U2, __enable_if_t<
+        is_constructible<_T1, _U1>::value &&
+        is_constructible<_T2, _U2>::value &&
+        !(is_convertible<_U1, _T1>::value &&
+          is_convertible<_U2, _T2>::value)
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
+    explicit
+    pair(tuple<_U1, _U2>&& __p)
+        : first(std::get<0>(std::move(__p))),
+          second(std::get<1>(std::move(__p))) {}
+
+
+    template<class _U1, class _U2, __enable_if_t<
+        is_assignable<_T1&, _U1 const&>::value &&
+        is_assignable<_T2&, _U2 const&>::value
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
+    pair& operator=(tuple<_U1, _U2> const& __p) {
+        first = std::get<0>(__p);
+        second = std::get<1>(__p);
+        return *this;
+    }
+
+    template<class _U1, class _U2, __enable_if_t<
+        is_assignable<_T1&, _U1&&>::value &&
+        is_assignable<_T2&, _U2&&>::value
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
+    pair& operator=(tuple<_U1, _U2>&& __p) {
+        first = std::get<0>(std::move(__p));
+        second = std::get<1>(std::move(__p));
+        return *this;
+    }
+
+    // from std::array
+    template<class _Up, __enable_if_t<
+        is_convertible<_Up const&, _T1>::value &&
+        is_convertible<_Up const&, _T2>::value
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
+    pair(array<_Up, 2> const& __p)
+        : first(__p[0]),
+          second(__p[1]) {}
+
+    template<class _Up, __enable_if_t<
+        is_constructible<_T1, _Up const&>::value &&
+        is_constructible<_T2, _Up const&>::value &&
+        !(is_convertible<_Up const&, _T1>::value &&
+          is_convertible<_Up const&, _T2>::value)
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
+    explicit
+    pair(array<_Up, 2> const& __p)
+        : first(__p[0]),
+          second(__p[1]) {}
+
+    template<class _Up, __enable_if_t<
+        is_convertible<_Up, _T1>::value &&
+        is_convertible<_Up, _T2>::value
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
+    pair(array<_Up, 2>&& __p)
+        : first(std::move(__p)[0]),
+          second(std::move(__p)[1]) {}
+
+    template<class _Up, __enable_if_t<
+        is_constructible<_T1, _Up>::value &&
+        is_constructible<_T2, _Up>::value &&
+        !(is_convertible<_Up, _T1>::value &&
+          is_convertible<_Up, _T2>::value)
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
+    explicit
+    pair(array<_Up, 2>&& __p)
+        : first(std::move(__p)[0]),
+          second(std::move(__p)[1]) {}
+
+
+    template<class _Up, __enable_if_t<
+        is_assignable<_T1&, _Up const&>::value &&
+        is_assignable<_T2&, _Up const&>::value
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
+    pair& operator=(array<_Up, 2> const& __p) {
+        first = std::get<0>(__p);
+        second = std::get<1>(__p);
+        return *this;
+    }
+
+    template<class _Up, __enable_if_t<
+        is_assignable<_T1&, _Up>::value &&
+        is_assignable<_T2&, _Up>::value
+    >* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
+    pair& operator=(array<_Up, 2>&& __p) {
+        first = std::get<0>(std::move(__p));
+        second = std::get<1>(std::move(__p));
         return *this;
     }
+#endif // _LIBCPP_STD_VER < 23
 #endif // _LIBCPP_CXX03_LANG
 
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20

diff  --git a/libcxx/include/utility b/libcxx/include/utility
index 1dc16b1158390..9ed7c6dac8ad2 100644
--- a/libcxx/include/utility
+++ b/libcxx/include/utility
@@ -84,14 +84,15 @@ struct pair
     explicit(see-below) constexpr pair();
     explicit(see-below) pair(const T1& x, const T2& y);                          // constexpr in C++14
     template <class U = T1, class V = T2> explicit(see-below) pair(U&&, V&&);    // constexpr in C++14
-    template <class U, class V> constexpr explicit(see below) pair(pair<U, V>&); // since C++23
+    template <class U, class V> constexpr explicit(see-below) pair(pair<U, V>&); // since C++23
     template <class U, class V> explicit(see-below) pair(const pair<U, V>& p);   // constexpr in C++14
     template <class U, class V> explicit(see-below) pair(pair<U, V>&& p);        // constexpr in C++14
     template <class U, class V>
-    constexpr explicit(see below) pair(const pair<U, V>&&);                      // since C++23
+    constexpr explicit(see-below) pair(const pair<U, V>&&);                      // since C++23
+    template <pair-like P> constexpr explicit(see-below) pair(P&&);              // since C++23
     template <class... Args1, class... Args2>
-        pair(piecewise_construct_t, tuple<Args1...> first_args,
-             tuple<Args2...> second_args);                                       // constexpr in C++20
+        pair(piecewise_construct_t, tuple<Args1...> first_args,                  // constexpr in C++20
+                                    tuple<Args2...> second_args);
 
     constexpr const pair& operator=(const pair& p) const;                        // since C++23
     template <class U, class V> pair& operator=(const pair<U, V>& p);            // constexpr in C++20
@@ -103,6 +104,8 @@ struct pair
     template <class U, class V> pair& operator=(pair<U, V>&& p);                 // constexpr in C++20
     template <class U, class V>
     constexpr const pair& operator=(pair<U, V>&& p) const;                       // since C++23
+    template <pair-like P> constexpr pair& operator=(P&&);                       // since C++23
+    template <pair-like P> constexpr const pair& operator=(P&&) const;           // since C++23
 
     void swap(pair& p) noexcept(is_nothrow_swappable_v<T1> &&
                                 is_nothrow_swappable_v<T2>);                     // constexpr in C++20

diff  --git a/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign.pair_like_rv.pass.cpp b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign.pair_like_rv.pass.cpp
new file mode 100644
index 0000000000000..92c6033512603
--- /dev/null
+++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign.pair_like_rv.pass.cpp
@@ -0,0 +1,111 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <utility>
+
+// template <class T1, class T2> struct pair
+
+//  template <pair-like P> constexpr pair& operator=(P&&);  // since C++23
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <ranges>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+constexpr bool test() {
+  // Make sure assignment works from array and tuple
+  {
+    // Check from std::array
+    {
+      std::array<int, 2> a = {1, 2};
+      std::pair<int, int> p;
+      std::same_as<std::pair<int, int>&> decltype(auto) result = (p = a);
+      assert(&result == &p);
+      assert(p.first == 1);
+      assert(p.second == 2);
+      static_assert(!std::is_assignable_v<std::pair<int, int>&, std::array<int, 1>>); // too small
+      static_assert( std::is_assignable_v<std::pair<int, int>&, std::array<int, 2>>); // works (test the test)
+      static_assert(!std::is_assignable_v<std::pair<int, int>&, std::array<int, 3>>); // too large
+    }
+
+    // Check from std::tuple
+    {
+      std::tuple<int, int> a = {1, 2};
+      std::pair<int, int> p;
+      std::same_as<std::pair<int, int>&> decltype(auto) result = (p = a);
+      assert(&result == &p);
+      assert(p.first == 1);
+      assert(p.second == 2);
+      static_assert(!std::is_assignable_v<std::pair<int, int>&, std::tuple<int>>); // too small
+      static_assert( std::is_assignable_v<std::pair<int, int>&, std::tuple<int, int>>); // works (test the test)
+      static_assert(!std::is_assignable_v<std::pair<int, int>&, std::tuple<int, int, int>>); // too large
+    }
+
+    // Make sure it works for ranges::subrange. This actually deserves an explanation: even though
+    // the assignment operator explicitly excludes ranges::subrange specializations, such assignments
+    // end up working because of ranges::subrange's implicit conversion to pair-like types.
+    // This test ensures that the interoperability works as intended.
+    {
+      struct Assignable {
+        int* ptr = nullptr;
+        Assignable() = default;
+        constexpr Assignable(int* p) : ptr(p) { } // enable `subrange::operator pair-like`
+        constexpr Assignable& operator=(Assignable const&) = default;
+      };
+      int data[] = {1, 2, 3, 4, 5};
+      std::ranges::subrange<int*> a(data);
+      std::pair<Assignable, Assignable> p;
+      std::same_as<std::pair<Assignable, Assignable>&> decltype(auto) result = (p = a);
+      assert(&result == &p);
+      assert(p.first.ptr == data);
+      assert(p.second.ptr == data + 5);
+    }
+  }
+
+  // Make sure we allow element conversion from a pair-like
+  {
+    std::tuple<int, char const*> a = {34, "hello world"};
+    std::pair<long, std::string> p;
+    std::same_as<std::pair<long, std::string>&> decltype(auto) result = (p = a);
+    assert(&result == &p);
+    assert(p.first == 34);
+    assert(p.second == std::string("hello world"));
+    static_assert(!std::is_assignable_v<std::pair<long, std::string>&, std::tuple<char*, std::string>>); // first not convertible
+    static_assert(!std::is_assignable_v<std::pair<long, std::string>&, std::tuple<long, void*>>); // second not convertible
+    static_assert( std::is_assignable_v<std::pair<long, std::string>&, std::tuple<long, std::string>>); // works (test the test)
+  }
+
+  // Make sure we forward the pair-like elements
+  {
+    struct NoCopy {
+      NoCopy() = default;
+      NoCopy(NoCopy const&) = delete;
+      NoCopy(NoCopy&&) = default;
+      NoCopy& operator=(NoCopy const&) = delete;
+      NoCopy& operator=(NoCopy&&) = default;
+    };
+    std::tuple<NoCopy, NoCopy> a;
+    std::pair<NoCopy, NoCopy> p;
+    p = std::move(a);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign.pair_like_rv_const.pass.cpp b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign.pair_like_rv_const.pass.cpp
new file mode 100644
index 0000000000000..1ebcb7ac9e2fe
--- /dev/null
+++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign.pair_like_rv_const.pass.cpp
@@ -0,0 +1,124 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// GCC 12 chokes on using a mutable variable inside a constexpr context
+// XFAIL: gcc-12
+
+// <utility>
+
+// template <class T1, class T2> struct pair
+
+//  template <pair-like P> constexpr const pair& operator=(P&&) const;  // since C++23
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <ranges>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+constexpr bool test() {
+  // Make sure assignment works from array and tuple
+  {
+    // Check from std::array
+    {
+      int x = 91, y = 92;
+      std::array<int, 2> a = {1, 2};
+      std::pair<int&, int&> const p = {x, y};
+      std::same_as<std::pair<int&, int&> const&> decltype(auto) result = (p = a);
+      assert(&result == &p);
+      assert(x == 1);
+      assert(y == 2);
+      static_assert(!std::is_assignable_v<std::pair<int&, int&> const&, std::array<int, 1>>); // too small
+      static_assert( std::is_assignable_v<std::pair<int&, int&> const&, std::array<int, 2>>); // works (test the test)
+      static_assert(!std::is_assignable_v<std::pair<int&, int&> const&, std::array<int, 3>>); // too large
+    }
+
+    // Check from std::tuple
+    {
+      int x = 91, y = 92;
+      std::tuple<int, int> a = {1, 2};
+      std::pair<int&, int&> const p = {x, y};
+      std::same_as<std::pair<int&, int&> const&> decltype(auto) result = (p = a);
+      assert(&result == &p);
+      assert(x == 1);
+      assert(y == 2);
+      static_assert(!std::is_assignable_v<std::pair<int&, int&> const&, std::tuple<int>>); // too small
+      static_assert( std::is_assignable_v<std::pair<int&, int&> const&, std::tuple<int, int>>); // works (test the test)
+      static_assert(!std::is_assignable_v<std::pair<int&, int&> const&, std::tuple<int, int, int>>); // too large
+    }
+
+    // Make sure it works for ranges::subrange. This actually deserves an explanation: even though
+    // the assignment operator explicitly excludes ranges::subrange specializations, such assignments
+    // end up working because of ranges::subrange's implicit conversion to pair-like types.
+    // This test ensures that the interoperability works as intended.
+    {
+      struct ConstAssignable {
+        mutable int* ptr = nullptr;
+        ConstAssignable() = default;
+        constexpr ConstAssignable(int* p) : ptr(p) { } // enable `subrange::operator pair-like`
+        constexpr ConstAssignable const& operator=(ConstAssignable const& other) const { ptr = other.ptr; return *this; }
+
+        constexpr ConstAssignable(ConstAssignable const&) = default; // defeat -Wdeprecated-copy
+        constexpr ConstAssignable& operator=(ConstAssignable const&) = default; // defeat -Wdeprecated-copy
+      };
+      int data[] = {1, 2, 3, 4, 5};
+      std::ranges::subrange<int*> a(data);
+      std::pair<ConstAssignable, ConstAssignable> const p;
+      std::same_as<std::pair<ConstAssignable, ConstAssignable> const&> decltype(auto) result = (p = a);
+      assert(&result == &p);
+      assert(p.first.ptr == data);
+      assert(p.second.ptr == data + 5);
+    }
+  }
+
+  // Make sure we allow element conversion from a pair-like
+  {
+    struct ConstAssignable {
+      mutable int val = 0;
+      ConstAssignable() = default;
+      constexpr ConstAssignable const& operator=(int v) const { val = v; return *this; }
+    };
+    std::tuple<int, int> a = {1, 2};
+    std::pair<ConstAssignable, ConstAssignable> const p;
+    std::same_as<std::pair<ConstAssignable, ConstAssignable> const&> decltype(auto) result = (p = a);
+    assert(&result == &p);
+    assert(p.first.val == 1);
+    assert(p.second.val == 2);
+    static_assert(!std::is_assignable_v<std::pair<ConstAssignable, ConstAssignable> const&, std::tuple<void*, int>>); // first not convertible
+    static_assert(!std::is_assignable_v<std::pair<ConstAssignable, ConstAssignable> const&, std::tuple<int, void*>>); // second not convertible
+    static_assert( std::is_assignable_v<std::pair<ConstAssignable, ConstAssignable> const&, std::tuple<int, int>>); // works (test the test)
+  }
+
+  // Make sure we forward the pair-like elements
+  {
+    struct NoCopy {
+      NoCopy() = default;
+      NoCopy(NoCopy const&) = delete;
+      NoCopy(NoCopy&&) = default;
+      NoCopy& operator=(NoCopy const&) = delete;
+      constexpr NoCopy const& operator=(NoCopy&&) const { return *this; }
+    };
+    std::tuple<NoCopy, NoCopy> a;
+    std::pair<NoCopy, NoCopy> const p;
+    p = std::move(a);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/utility/pairs/pairs.pair/ctor.pair_like.pass.cpp b/libcxx/test/std/utilities/utility/pairs/pairs.pair/ctor.pair_like.pass.cpp
new file mode 100644
index 0000000000000..3362a872a5857
--- /dev/null
+++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/ctor.pair_like.pass.cpp
@@ -0,0 +1,132 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <utility>
+
+// template <class T1, class T2> struct pair
+
+// template <pair-like P>
+// constexpr explicit(see-below) pair(P&&); // since C++23
+
+#include <array>
+#include <cassert>
+#include <ranges>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+constexpr bool test() {
+  // Make sure construction works from array, tuple, and ranges::subrange
+  {
+    // Check from std::array
+    {
+      std::array<int, 2> a = {1, 2};
+      std::pair<int, int> p(a);
+      assert(p.first == 1);
+      assert(p.second == 2);
+      static_assert(!std::is_constructible_v<std::pair<int, int>, std::array<int, 1>>); // too small
+      static_assert( std::is_constructible_v<std::pair<int, int>, std::array<int, 2>>); // works (test the test)
+      static_assert(!std::is_constructible_v<std::pair<int, int>, std::array<int, 3>>); // too large
+    }
+
+    // Check from std::tuple
+    {
+      std::tuple<int, int> a = {1, 2};
+      std::pair<int, int> p(a);
+      assert(p.first == 1);
+      assert(p.second == 2);
+      static_assert(!std::is_constructible_v<std::pair<int, int>, std::tuple<int>>); // too small
+      static_assert( std::is_constructible_v<std::pair<int, int>, std::tuple<int, int>>); // works (test the test)
+      static_assert(!std::is_constructible_v<std::pair<int, int>, std::tuple<int, int, int>>); // too large
+    }
+
+    // Check from ranges::subrange
+    {
+      int data[] = {1, 2, 3, 4, 5};
+      std::ranges::subrange a(data);
+      {
+        std::pair<int*, int*> p(a);
+        assert(p.first == data + 0);
+        assert(p.second == data + 5);
+      }
+      {
+        std::pair<int*, int*> p{a};
+        assert(p.first == data + 0);
+        assert(p.second == data + 5);
+      }
+      {
+        std::pair<int*, int*> p = a;
+        assert(p.first == data + 0);
+        assert(p.second == data + 5);
+      }
+    }
+  }
+
+  // Make sure we allow element conversion from a pair-like
+  {
+    std::tuple<int, char const*> a = {34, "hello world"};
+    std::pair<long, std::string> p(a);
+    assert(p.first == 34);
+    assert(p.second == std::string("hello world"));
+    static_assert(!std::is_constructible_v<std::pair<long, std::string>, std::tuple<char*, std::string>>); // first not convertible
+    static_assert(!std::is_constructible_v<std::pair<long, std::string>, std::tuple<long, void*>>); // second not convertible
+    static_assert( std::is_constructible_v<std::pair<long, std::string>, std::tuple<long, std::string>>); // works (test the test)
+  }
+
+  // Make sure we forward the pair-like elements
+  {
+    struct NoCopy {
+      NoCopy() = default;
+      NoCopy(NoCopy const&) = delete;
+      NoCopy(NoCopy&&) = default;
+    };
+    std::tuple<NoCopy, NoCopy> a;
+    std::pair<NoCopy, NoCopy> p(std::move(a));
+    (void)p;
+  }
+
+  // Make sure the constructor is implicit iff both elements can be converted
+  {
+    struct To { };
+    struct FromImplicit {
+      constexpr operator To() const { return To{}; }
+    };
+    struct FromExplicit {
+      constexpr explicit operator To() const { return To{}; }
+    };
+    // If both are convertible, the constructor is not explicit
+    {
+      std::tuple<FromImplicit, float> a = {FromImplicit{}, 2.3f};
+      std::pair<To, double> p = a;
+      (void)p;
+      static_assert(std::is_convertible_v<std::tuple<FromImplicit, float>, std::pair<To, double>>);
+    }
+    // Otherwise, the constructor is explicit
+    {
+      static_assert( std::is_constructible_v<std::pair<To, int>, std::tuple<FromExplicit, int>>);
+      static_assert(!std::is_convertible_v<std::tuple<FromExplicit, int>, std::pair<To, int>>);
+
+      static_assert( std::is_constructible_v<std::pair<int, To>, std::tuple<int, FromExplicit>>);
+      static_assert(!std::is_convertible_v<std::tuple<int, FromExplicit>, std::pair<int, To>>);
+
+      static_assert( std::is_constructible_v<std::pair<To, To>, std::tuple<FromExplicit, FromExplicit>>);
+      static_assert(!std::is_convertible_v<std::tuple<FromExplicit, FromExplicit>, std::pair<To, To>>);
+    }
+  }
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}


        


More information about the libcxx-commits mailing list