diff --git a/libcxx/docs/ReleaseNotes.rst b/libcxx/docs/ReleaseNotes.rst --- a/libcxx/docs/ReleaseNotes.rst +++ b/libcxx/docs/ReleaseNotes.rst @@ -52,6 +52,11 @@ - The ```` header has been removed in this release. The ```` header has been shipping since LLVM 14, so the Coroutines TS implementation is being removed per our policy for removing TSes. +- Long-standing extensions for ``std::pair``'s constructors and assignment operators that accepted + tuple-like types have been removed. If you need to construct a pair or assign to a pair from a + tuple-like type, please make sure to compile with C++23, since that Standard provides support for + that out-of-the-box. This extension was removed due to the difficulty to reconcile changes in new + standards with this extension. - Several incidental transitive includes have been removed from libc++. Those includes are removed based on the language version used. Incidental transitive diff --git a/libcxx/include/__tuple_dir/sfinae_helpers.h b/libcxx/include/__tuple_dir/sfinae_helpers.h --- a/libcxx/include/__tuple_dir/sfinae_helpers.h +++ b/libcxx/include/__tuple_dir/sfinae_helpers.h @@ -108,19 +108,6 @@ typedef _LIBCPP_NODEBUG typename tuple_element<_Ip, __tuple_types<_Tp...> >::type type; }; -template -struct __tuple_like_with_size_imp : false_type {}; - -template -struct __tuple_like_with_size_imp - : integral_constant {}; - -template > -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 constexpr bool __enable_explicit_default() { return false; } diff --git a/libcxx/include/__utility/pair.h b/libcxx/include/__utility/pair.h --- a/libcxx/include/__utility/pair.h +++ b/libcxx/include/__utility/pair.h @@ -11,17 +11,20 @@ #include <__compare/common_comparison_category.h> #include <__compare/synth_three_way.h> +#include <__concepts/different_from.h> #include <__config> #include <__functional/unwrap_ref.h> #include <__fwd/get.h> #include <__fwd/tuple.h> #include <__tuple_dir/sfinae_helpers.h> #include <__tuple_dir/tuple_element.h> +#include <__tuple_dir/pair_like.h> #include <__tuple_dir/tuple_indices.h> #include <__tuple_dir/tuple_size.h> #include <__type_traits/common_reference.h> #include <__type_traits/common_type.h> #include <__type_traits/conditional.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> @@ -57,6 +60,14 @@ __non_trivially_copyable_base(__non_trivially_copyable_base const&) _NOEXCEPT {} }; +#if _LIBCPP_STD_VER >= 23 +template +struct __is_specialization_of_subrange : false_type {}; + +template +struct __is_specialization_of_subrange> : true_type {}; +#endif + template struct _LIBCPP_TEMPLATE_VIS pair #if defined(_LIBCPP_DEPRECATED_ABI_DISABLE_PAIR_TRIVIAL_COPY_CTOR) @@ -131,32 +142,6 @@ using _CheckArgsDep _LIBCPP_NODEBUG = typename conditional< _MaybeEnable, _CheckArgs, __check_tuple_constructor_fail>::type; - struct _CheckTupleLikeConstructor { - template - static constexpr bool __enable_implicit() { - return __tuple_convertible<_Tuple, pair>::value; - } - - template - static constexpr bool __enable_explicit() { - return __tuple_constructible<_Tuple, pair>::value - && !__tuple_convertible<_Tuple, pair>::value; - } - - template - static constexpr bool __enable_assign() { - return __tuple_assignable<_Tuple, pair>::value; - } - }; - - template - using _CheckTLC _LIBCPP_NODEBUG = __conditional_t< - __tuple_like_with_size<_Tuple, 2>::value - && !is_same::type, pair>::value, - _CheckTupleLikeConstructor, - __check_tuple_constructor_fail - >; - template::__enable_explicit_default() >::type* = nullptr> @@ -278,21 +263,22 @@ : first(std::move(__p.first)), second(std::move(__p.second)) {} #endif - template::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 >::value == 2> + static constexpr bool __pair_like_explicit_wknd = !is_convertible_v(std::declval<_PairLike&&>())), first_type> || + !is_convertible_v(std::declval<_PairLike&&>())), second_type>; - template::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 + static constexpr bool __pair_like_explicit_wknd<_PairLike, false> = false; + + template <__pair_like _PairLike> + requires(is_constructible_v(std::declval<_PairLike&&>()))> && + is_constructible_v(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 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 @@ -330,7 +316,29 @@ return *this; } -#if _LIBCPP_STD_VER >= 23 + template ::value && + is_assignable::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 ::value && + is_assignable::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 && @@ -372,17 +380,29 @@ second = std::forward<_U2>(__p.second); return *this; } -#endif // _LIBCPP_STD_VER >= 23 - template ::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(__different_from<_PairLike, pair> && + !__is_specialization_of_subrange>::value && + is_assignable_v(std::declval<_PairLike>()))> && + is_assignable_v(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(__different_from<_PairLike, pair> && + !__is_specialization_of_subrange>::value && + is_assignable_v(std::declval<_PairLike>()))> && + is_assignable_v(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 #endif // _LIBCPP_CXX03_LANG _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 diff --git a/libcxx/include/utility b/libcxx/include/utility --- a/libcxx/include/utility +++ b/libcxx/include/utility @@ -84,14 +84,15 @@ explicit(see-below) constexpr pair(); explicit(see-below) pair(const T1& x, const T2& y); // constexpr in C++14 template explicit(see-below) pair(U&&, V&&); // constexpr in C++14 - template constexpr explicit(see below) pair(pair&); // since C++23 + template constexpr explicit(see-below) pair(pair&); // since C++23 template explicit(see-below) pair(const pair& p); // constexpr in C++14 template explicit(see-below) pair(pair&& p); // constexpr in C++14 template - constexpr explicit(see below) pair(const pair&&); // since C++23 + constexpr explicit(see-below) pair(const pair&&); // since C++23 + template constexpr explicit(see-below) pair(P&&); // since C++23 template - pair(piecewise_construct_t, tuple first_args, - tuple second_args); // constexpr in C++20 + pair(piecewise_construct_t, tuple first_args, // constexpr in C++20 + tuple second_args); constexpr const pair& operator=(const pair& p) const; // since C++23 template pair& operator=(const pair& p); // constexpr in C++20 @@ -103,6 +104,8 @@ template pair& operator=(pair&& p); // constexpr in C++20 template constexpr const pair& operator=(pair&& p) const; // since C++23 + template constexpr pair& operator=(P&&); // since C++23 + template constexpr const pair& operator=(P&&) const; // since C++23 void swap(pair& p) noexcept(is_nothrow_swappable_v && is_nothrow_swappable_v); // 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 --- /dev/null +++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign.pair_like_rv.pass.cpp @@ -0,0 +1,105 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template struct pair + +// template constexpr pair& operator=(P&&); // since C++23 + +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr bool test() { + // Make sure assignment works from array and tuple + { + // Check from std::array + { + std::array a = {1, 2}; + std::pair p; + std::same_as&> decltype(auto) result = (p = a); + assert(&result == &p); + assert(p.first == 1); + assert(p.second == 2); + static_assert(!std::is_assignable_v&, std::array>); // too small + static_assert( std::is_assignable_v&, std::array>); // works (test the test) + static_assert(!std::is_assignable_v&, std::array>); // too large + } + + // Check from std::tuple + { + std::tuple a = {1, 2}; + std::pair p; + std::same_as&> decltype(auto) result = (p = a); + assert(&result == &p); + assert(p.first == 1); + assert(p.second == 2); + static_assert(!std::is_assignable_v&, std::tuple>); // too small + static_assert( std::is_assignable_v&, std::tuple>); // works (test the test) + static_assert(!std::is_assignable_v&, std::tuple>); // too large + } + + // Make sure it works for ranges::subrange. This actually merits 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. + { + int data[] = {1, 2, 3, 4, 5}; + std::ranges::subrange a(data); + std::pair p; + std::same_as&> decltype(auto) result = (p = a); + assert(&result == &p); + assert(p.first == data); + assert(p.second == data + 5); + } + } + + // Make sure we allow element conversion from a pair-like + { + std::tuple a = {34, "hello world"}; + std::pair p; + std::same_as&> 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::tuple>); // first not convertible + static_assert(!std::is_assignable_v&, std::tuple>); // second not convertible + static_assert( std::is_assignable_v&, std::tuple>); // 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 a; + std::pair 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 --- /dev/null +++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign.pair_like_rv_const.pass.cpp @@ -0,0 +1,109 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template struct pair + +// template constexpr const pair& operator=(P&&) const; // since C++23 + +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr bool test() { + // Make sure assignment works from array and tuple + { + // Check from std::array + { + int x = 91, y = 92; + std::array a = {1, 2}; + std::pair const p = {x, y}; + std::same_as const&> decltype(auto) result = (p = a); + assert(&result == &p); + assert(x == 1); + assert(y == 2); + static_assert(!std::is_assignable_v const&, std::array>); // too small + static_assert( std::is_assignable_v const&, std::array>); // works (test the test) + static_assert(!std::is_assignable_v const&, std::array>); // too large + } + + // Check from std::tuple + { + int x = 91, y = 92; + std::tuple a = {1, 2}; + std::pair const p = {x, y}; + std::same_as const&> decltype(auto) result = (p = a); + assert(&result == &p); + assert(x == 1); + assert(y == 2); + static_assert(!std::is_assignable_v const&, std::tuple>); // too small + static_assert( std::is_assignable_v const&, std::tuple>); // works (test the test) + static_assert(!std::is_assignable_v const&, std::tuple>); // too large + } + + // Make sure it doesn't work for `ranges::subrange`, since it is called out explicitly. + { + struct ConstAssignable { + mutable int* ptr = nullptr; + ConstAssignable() = default; + constexpr ConstAssignable const& operator=(int* p) const { ptr = p; return *this; } + }; + static_assert( std::is_assignable_v const&, std::tuple>); // test the test + static_assert(!std::is_assignable_v const&, std::ranges::subrange>); + } + } + + // 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 a = {1, 2}; + std::pair const p; + std::same_as 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 const&, std::tuple>); // first not convertible + static_assert(!std::is_assignable_v const&, std::tuple>); // second not convertible + static_assert( std::is_assignable_v const&, std::tuple>); // 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 a; + std::pair 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 --- /dev/null +++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/ctor.pair_like.pass.cpp @@ -0,0 +1,120 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template struct pair + +// template +// constexpr explicit(see-below) pair(P&&); // since C++23 + +#include +#include +#include +#include +#include +#include +#include + +constexpr bool test() { + // Make sure construction works from array, tuple, and ranges::subrange + { + // Check from std::array + { + std::array a = {1, 2}; + std::pair p(a); + assert(p.first == 1); + assert(p.second == 2); + static_assert(!std::is_constructible_v, std::array>); // too small + static_assert( std::is_constructible_v, std::array>); // works (test the test) + static_assert(!std::is_constructible_v, std::array>); // too large + } + + // Check from std::tuple + { + std::tuple a = {1, 2}; + std::pair p(a); + assert(p.first == 1); + assert(p.second == 2); + static_assert(!std::is_constructible_v, std::tuple>); // too small + static_assert( std::is_constructible_v, std::tuple>); // works (test the test) + static_assert(!std::is_constructible_v, std::tuple>); // too large + } + + // Check from ranges::subrange + { + int data[] = {1, 2, 3, 4, 5}; + std::ranges::subrange a(data); + std::pair p(a); + assert(p.first == data + 0); + assert(p.second == data + 5); + } + } + + // Make sure we allow element conversion from a pair-like + { + std::tuple a = {34, "hello world"}; + std::pair p(a); + assert(p.first == 34); + assert(p.second == std::string("hello world")); + static_assert(!std::is_constructible_v, std::tuple>); // first not convertible + static_assert(!std::is_constructible_v, std::tuple>); // second not convertible + static_assert( std::is_constructible_v, std::tuple>); // 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 a; + std::pair 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 a = {{}, 2.3f}; + std::pair p = a; + (void)p; + static_assert(std::is_convertible_v, std::pair>); + } + // Otherwise, the constructor is explicit + { + static_assert( std::is_constructible_v, std::tuple>); + static_assert(!std::is_convertible_v, std::pair>); + + static_assert( std::is_constructible_v, std::tuple>); + static_assert(!std::is_convertible_v, std::pair>); + + static_assert( std::is_constructible_v, std::tuple>); + static_assert(!std::is_convertible_v, std::pair>); + } + } + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +}