[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