diff --git a/libcxx/include/__tuple/sfinae_helpers.h b/libcxx/include/__tuple/sfinae_helpers.h --- a/libcxx/include/__tuple/sfinae_helpers.h +++ b/libcxx/include/__tuple/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 _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 --- a/libcxx/include/__utility/pair.h +++ b/libcxx/include/__utility/pair.h @@ -11,10 +11,12 @@ #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/pair_like.h> #include <__tuple/sfinae_helpers.h> #include <__tuple/tuple_element.h> #include <__tuple/tuple_indices.h> @@ -23,6 +25,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> @@ -58,6 +61,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) @@ -132,32 +143,6 @@ using _CheckArgsDep _LIBCPP_NODEBUG = typename conditional< _MaybeEnable, _CheckArgs, __check_tuple_constructor_fail>::type; - struct _CheckTupleLikeConstructor { - template - static _LIBCPP_HIDE_FROM_ABI constexpr bool __enable_implicit() { - return __tuple_convertible<_Tuple, pair>::value; - } - - template - static _LIBCPP_HIDE_FROM_ABI constexpr bool __enable_explicit() { - return __tuple_constructible<_Tuple, pair>::value - && !__tuple_convertible<_Tuple, pair>::value; - } - - template - static _LIBCPP_HIDE_FROM_ABI 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<__decay_t<_Tuple>, pair>::value, - _CheckTupleLikeConstructor, - __check_tuple_constructor_fail - >; - template::__enable_explicit_default() >::type* = nullptr> @@ -279,21 +264,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 @@ -331,7 +317,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 && @@ -373,19 +381,167 @@ 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 + // 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::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::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::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::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::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::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::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::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::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::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::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::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 + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void swap(pair& __p) _NOEXCEPT_(__is_nothrow_swappable::value && 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,112 @@ +//===----------------------------------------------------------------------===// +// +// 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. + { + struct Assignable { + int* ptr = nullptr; + Assignable() = default; + constexpr Assignable(int* p) : ptr(p) { } // enable `subrange::operator pair-like` + constexpr Assignable& operator=(int* p) { ptr = p; return *this; } + constexpr Assignable& operator=(Assignable const&) = default; + }; + 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.ptr == data); + assert(p.second.ptr == 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,119 @@ +//===----------------------------------------------------------------------===// +// +// 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 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. + { + struct ConstAssignable { + mutable int* ptr = nullptr; + ConstAssignable() = default; + constexpr ConstAssignable(int* p) : ptr(p) { } // enable `subrange::operator pair-like` + constexpr ConstAssignable const& operator=(int* p) const { ptr = p; return *this; } + constexpr ConstAssignable const& operator=(ConstAssignable const& other) const { ptr = other.ptr; return *this; } + }; + 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.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 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,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 + +// + +// 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); + } + { + std::pair p{a}; + assert(p.first == data + 0); + assert(p.second == data + 5); + } + { + 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; +}