P2165R4: Compatibility Between `tuple`, `pair`, And tuple-like Objects (changes to `pair` only) (#3323)

Co-authored-by: Stephan T. Lavavej <stl@nuwen.net>
This commit is contained in:
Jakub Mazurkiewicz 2023-01-22 07:10:25 +01:00 коммит произвёл GitHub
Родитель 31defd3719
Коммит 10722961ab
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 318 добавлений и 54 удалений

Просмотреть файл

@ -429,6 +429,9 @@ namespace ranges {
_EXPORT_STD using ranges::get;
template <class _It, class _Se, ranges::subrange_kind _Ki>
inline constexpr bool _Is_subrange_v<ranges::subrange<_It, _Se, _Ki>> = true;
template <class _It, class _Se, ranges::subrange_kind _Ki>
struct tuple_size<ranges::subrange<_It, _Se, _Ki>> : integral_constant<size_t, 2> {};

Просмотреть файл

@ -2713,11 +2713,6 @@ namespace ranges {
inline constexpr bool enable_borrowed_range<take_view<_Rng>> = enable_borrowed_range<_Rng>;
namespace views {
template <class>
inline constexpr bool _Is_subrange = false;
template <class _It, class _Se, subrange_kind _Ki>
inline constexpr bool _Is_subrange<subrange<_It, _Se, _Ki>> = true;
template <class _Rng>
concept _Random_sized_range = random_access_range<_Rng> && sized_range<_Rng>;
@ -2745,7 +2740,7 @@ namespace ranges {
} else if constexpr (_Random_sized_range<_Ty> && _Is_specialization_v<_Ty, iota_view>) {
return {_St::_Reconstruct_iota_view,
noexcept(_RANGES begin(_STD declval<_Rng&>()) + _RANGES distance(_STD declval<_Rng&>()))};
} else if constexpr (_Random_sized_range<_Ty> && _Is_subrange<_Ty>) {
} else if constexpr (_Random_sized_range<_Ty> && _Is_subrange_v<_Ty>) {
return {_St::_Reconstruct_subrange,
noexcept(subrange(_RANGES begin(_STD declval<_Rng&>()),
_RANGES begin(_STD declval<_Rng&>()) + _RANGES distance(_STD declval<_Rng&>())))};
@ -3121,7 +3116,7 @@ namespace ranges {
return {_St::_Reconstruct_span, true};
} else if constexpr (_Is_specialization_v<_Ty, basic_string_view>) {
return {_St::_Reconstruct_other, true};
} else if constexpr (_Random_sized_range<_Ty> && _Is_subrange<_Ty>) {
} else if constexpr (_Random_sized_range<_Ty> && _Is_subrange_v<_Ty>) {
if constexpr (sized_sentinel_for<sentinel_t<_Ty>, iterator_t<_Ty>>) {
return {_St::_Reconstruct_subrange,
noexcept(_Ty(_RANGES begin(_STD declval<_Rng&>()) + _RANGES distance(_STD declval<_Rng&>()),

Просмотреть файл

@ -222,9 +222,6 @@ struct _Span_extent_type<_Ty, dynamic_extent> {
size_t _Mysize{0};
};
_EXPORT_STD template <class _Ty, size_t _Size>
class array;
_EXPORT_STD template <class _Ty, size_t _Extent>
class span;
@ -242,12 +239,6 @@ inline constexpr bool _Is_span_v = false;
template <class _Ty, size_t _Extent>
inline constexpr bool _Is_span_v<span<_Ty, _Extent>> = true;
template <class>
inline constexpr bool _Is_std_array_v = false;
template <class _Ty, size_t _Size>
inline constexpr bool _Is_std_array_v<array<_Ty, _Size>> = true;
// clang-format off
template <class _It, class _Ty>
concept _Span_compatible_iterator = contiguous_iterator<_It>

Просмотреть файл

@ -179,9 +179,6 @@ struct _Alloc_unpack_tuple_t {
explicit _Alloc_unpack_tuple_t() = default;
}; // tag type to disambiguate construction (from an allocator and unpacking a tuple/pair)
_EXPORT_STD template <class... _Types>
class tuple;
template <>
class tuple<> { // empty tuple
public:
@ -824,21 +821,6 @@ _NODISCARD constexpr tuple<_Types&&...> forward_as_tuple(_Types&&... _Args) noex
return tuple<_Types&&...>(_STD forward<_Types>(_Args)...);
}
_EXPORT_STD template <class _Ty, size_t _Size>
class array;
_EXPORT_STD template <size_t _Idx, class _Ty, size_t _Size>
_NODISCARD constexpr _Ty& get(array<_Ty, _Size>& _Arr) noexcept;
_EXPORT_STD template <size_t _Idx, class _Ty, size_t _Size>
_NODISCARD constexpr const _Ty& get(const array<_Ty, _Size>& _Arr) noexcept;
_EXPORT_STD template <size_t _Idx, class _Ty, size_t _Size>
_NODISCARD constexpr _Ty&& get(array<_Ty, _Size>&& _Arr) noexcept;
_EXPORT_STD template <size_t _Idx, class _Ty, size_t _Size>
_NODISCARD constexpr const _Ty&& get(const array<_Ty, _Size>&& _Arr) noexcept;
template <class _Ty, class _Kx_arg, class _Ix_arg, size_t _Ix_next, class... _Sequences>
struct _Tuple_cat2;

Просмотреть файл

@ -134,12 +134,87 @@ struct uses_allocator : _Has_allocator_type<_Ty, _Alloc>::type {
_EXPORT_STD template <class _Ty, class _Alloc>
_INLINE_VAR constexpr bool uses_allocator_v = uses_allocator<_Ty, _Alloc>::value;
_EXPORT_STD template <class...>
_EXPORT_STD template <class... _Types>
class tuple;
_EXPORT_STD template <class _Ty1, class _Ty2>
struct pair;
_EXPORT_STD template <class _Ty, size_t _Size>
class array;
_EXPORT_STD template <class _Tuple>
struct tuple_size;
_EXPORT_STD template <class _Ty>
_INLINE_VAR constexpr size_t tuple_size_v = tuple_size<_Ty>::value;
_EXPORT_STD template <size_t _Index, class _Tuple>
struct tuple_element;
_EXPORT_STD template <size_t _Index, class _Tuple>
using tuple_element_t = typename tuple_element<_Index, _Tuple>::type;
_EXPORT_STD /* TRANSITION, VSO-1538698 */ template <size_t _Index, class... _Types>
_NODISCARD constexpr auto&& _Tuple_get(tuple<_Types...>&& _Tuple) noexcept;
_EXPORT_STD template <size_t _Index, class... _Types>
_NODISCARD constexpr tuple_element_t<_Index, tuple<_Types...>>& get(tuple<_Types...>& _Tuple) noexcept;
_EXPORT_STD template <size_t _Index, class... _Types>
_NODISCARD constexpr const tuple_element_t<_Index, tuple<_Types...>>& get(const tuple<_Types...>& _Tuple) noexcept;
_EXPORT_STD template <size_t _Index, class... _Types>
_NODISCARD constexpr tuple_element_t<_Index, tuple<_Types...>>&& get(tuple<_Types...>&& _Tuple) noexcept;
_EXPORT_STD template <size_t _Index, class... _Types>
_NODISCARD constexpr const tuple_element_t<_Index, tuple<_Types...>>&& get(const tuple<_Types...>&& _Tuple) noexcept;
_EXPORT_STD template <size_t _Idx, class _Ty, size_t _Size>
_NODISCARD constexpr _Ty& get(array<_Ty, _Size>& _Arr) noexcept;
_EXPORT_STD template <size_t _Idx, class _Ty, size_t _Size>
_NODISCARD constexpr const _Ty& get(const array<_Ty, _Size>& _Arr) noexcept;
_EXPORT_STD template <size_t _Idx, class _Ty, size_t _Size>
_NODISCARD constexpr _Ty&& get(array<_Ty, _Size>&& _Arr) noexcept;
_EXPORT_STD template <size_t _Idx, class _Ty, size_t _Size>
_NODISCARD constexpr const _Ty&& get(const array<_Ty, _Size>&& _Arr) noexcept;
#ifdef __cpp_lib_concepts
template <class _Ty1, class _Ty2>
concept _Different_from = (!same_as<remove_cvref_t<_Ty1>, remove_cvref_t<_Ty2>>);
template <class>
inline constexpr bool _Is_std_array_v = false;
template <class _Ty, size_t _Size>
inline constexpr bool _Is_std_array_v<array<_Ty, _Size>> = true;
template <class>
inline constexpr bool _Is_subrange_v = false;
#if _HAS_CXX23
template <class _Ty>
inline constexpr bool _Tuple_like_impl =
_Is_specialization_v<_Ty, tuple> || _Is_specialization_v<_Ty, pair> || _Is_std_array_v<_Ty> || _Is_subrange_v<_Ty>;
template <class _Ty>
concept _Tuple_like = _Tuple_like_impl<remove_cvref_t<_Ty>>;
template <class _Ty>
concept _Pair_like = _Tuple_like<_Ty> && tuple_size_v<remove_cvref_t<_Ty>> == 2;
#ifdef __clang__ // TRANSITION, LLVM-59827
template <class _PairLike, class _Ty1, class _Ty2>
concept _Can_construct_from_pair_like =
_Pair_like<_PairLike> && is_constructible_v<_Ty1, decltype(_STD get<0>(_STD declval<_PairLike>()))>
&& is_constructible_v<_Ty2, decltype(_STD get<1>(_STD declval<_PairLike>()))>;
#endif // __clang__
#endif // _HAS_CXX23
#endif // __cpp_lib_concepts
_EXPORT_STD template <class _Ty1, class _Ty2>
struct pair { // store a pair of values
using first_type = _Ty1;
@ -207,6 +282,22 @@ struct pair { // store a pair of values
pair(const pair<_Other1, _Other2>&& _Right) noexcept(is_nothrow_constructible_v<_Ty1, const _Other1>&&
is_nothrow_constructible_v<_Ty2, const _Other2>) // strengthened
: first(_STD forward<const _Other1>(_Right.first)), second(_STD forward<const _Other2>(_Right.second)) {}
#ifdef __cpp_lib_concepts
#ifdef __clang__ // TRANSITION, LLVM-59827
template <class _Other, enable_if_t<_Can_construct_from_pair_like<_Other, _Ty1, _Ty2>, int> = 0>
#else // ^^^ workaround / no workaround vvv
template <_Pair_like _Other>
requires conjunction_v<is_constructible<_Ty1, decltype(_STD get<0>(_STD declval<_Other>()))>,
is_constructible<_Ty2, decltype(_STD get<1>(_STD declval<_Other>()))>>
#endif // __clang__
constexpr explicit(!conjunction_v<is_convertible<decltype(_STD get<0>(_STD declval<_Other>())), _Ty1>,
is_convertible<decltype(_STD get<1>(_STD declval<_Other>())), _Ty2>>)
pair(_Other&& _Right) noexcept(is_nothrow_constructible_v<_Ty1, decltype(_STD get<0>(_STD declval<_Other>()))>&&
is_nothrow_constructible_v<_Ty2, decltype(_STD get<1>(_STD declval<_Other>()))>) // strengthened
: first(_STD get<0>(_STD forward<_Other>(_Right))), second(_STD get<1>(_STD forward<_Other>(_Right))) {
}
#endif // __cpp_lib_concepts
#endif // _HAS_CXX23
template <class _Tuple1, class _Tuple2, size_t... _Indexes1, size_t... _Indexes2>
@ -318,6 +409,32 @@ struct pair { // store a pair of values
second = _STD forward<_Other2>(_Right.second);
return *this;
}
#ifdef __cpp_lib_concepts
template <_Pair_like _Other>
requires _Different_from<_Other, pair> && (!_Is_subrange_v<remove_cvref_t<_Other>>)
&& is_assignable_v<_Ty1&, decltype(_STD get<0>(_STD declval<_Other>()))>
&& is_assignable_v<_Ty2&, decltype(_STD get<1>(_STD declval<_Other>()))>
constexpr pair& operator=(_Other&& _Right) noexcept(
is_nothrow_assignable_v<_Ty1&, decltype(_STD get<0>(_STD declval<_Other>()))>&&
is_nothrow_assignable_v<_Ty2&, decltype(_STD get<1>(_STD declval<_Other>()))>) /* strengthened */ {
first = _STD get<0>(_STD forward<_Other>(_Right));
second = _STD get<1>(_STD forward<_Other>(_Right));
return *this;
}
template <_Pair_like _Other>
requires _Different_from<_Other, pair> && (!_Is_subrange_v<remove_cvref_t<_Other>>)
&& is_assignable_v<const _Ty1&, decltype(_STD get<0>(_STD declval<_Other>()))>
&& is_assignable_v<const _Ty2&, decltype(_STD get<1>(_STD declval<_Other>()))>
constexpr const pair& operator=(_Other&& _Right) const noexcept(
is_nothrow_assignable_v<const _Ty1&, decltype(_STD get<0>(_STD declval<_Other>()))>&&
is_nothrow_assignable_v<const _Ty2&, decltype(_STD get<1>(_STD declval<_Other>()))>) /* strengthened */ {
first = _STD get<0>(_STD forward<_Other>(_Right));
second = _STD get<1>(_STD forward<_Other>(_Right));
return *this;
}
#endif // __cpp_lib_concepts
#endif // _HAS_CXX23
_CONSTEXPR20 void swap(pair& _Right) noexcept(
@ -469,9 +586,6 @@ namespace _CXX20_DEPRECATE_REL_OPS rel_ops {
}
} // namespace _CXX20_DEPRECATE_REL_OPS rel_ops
_EXPORT_STD template <class _Tuple>
struct tuple_size;
template <class _Tuple, class = void>
struct _Tuple_size_sfinae {}; // selected when tuple_size<_Tuple>::value isn't well-formed
@ -488,12 +602,6 @@ struct _CXX20_DEPRECATE_VOLATILE tuple_size<volatile _Tuple> : _Tuple_size_sfina
template <class _Tuple>
struct _CXX20_DEPRECATE_VOLATILE tuple_size<const volatile _Tuple> : _Tuple_size_sfinae<_Tuple> {}; // ignore cv
_EXPORT_STD template <class _Ty>
_INLINE_VAR constexpr size_t tuple_size_v = tuple_size<_Ty>::value;
_EXPORT_STD template <size_t _Index, class _Tuple>
struct tuple_element;
template <size_t _Index, class _Tuple>
struct _MSVC_KNOWN_SEMANTICS tuple_element<_Index, const _Tuple> : tuple_element<_Index, _Tuple> {
using _Mybase = tuple_element<_Index, _Tuple>;
@ -514,12 +622,6 @@ struct _CXX20_DEPRECATE_VOLATILE _MSVC_KNOWN_SEMANTICS tuple_element<_Index, con
using type = add_cv_t<typename _Mybase::type>;
};
_EXPORT_STD template <size_t _Index, class _Tuple>
using tuple_element_t = typename tuple_element<_Index, _Tuple>::type;
_EXPORT_STD template <class _Ty, size_t _Size>
class array;
template <class _Ty, size_t _Size>
struct tuple_size<array<_Ty, _Size>> : integral_constant<size_t, _Size> {}; // size of array

Просмотреть файл

@ -1755,9 +1755,6 @@ _NODISCARD constexpr const _Elem* data(initializer_list<_Elem> _Ilist) noexcept
}
#ifdef __cpp_lib_concepts
template <class _Ty1, class _Ty2>
concept _Different_from = (!same_as<remove_cvref_t<_Ty1>, remove_cvref_t<_Ty2>>);
#if _HAS_CXX23
_EXPORT_STD template <indirectly_readable _Ty>
using iter_const_reference_t = common_reference_t<const iter_value_t<_Ty>&&, iter_reference_t<_Ty>>;

Просмотреть файл

@ -322,7 +322,7 @@
// P2077R3 Heterogeneous Erasure Overloads For Associative Containers
// P2136R3 invoke_r()
// P2165R4 Compatibility Between tuple, pair, And tuple-like Objects
// (changes to views::zip only)
// (changes to views::zip and pair only)
// P2166R1 Prohibiting basic_string And basic_string_view Construction From nullptr
// P2186R2 Removing Garbage Collection Support
// P2273R3 constexpr unique_ptr

Просмотреть файл

@ -551,6 +551,7 @@ tests\P1899R3_views_stride_death
tests\P1951R1_default_arguments_pair_forward_ctor
tests\P2136R3_invoke_r
tests\P2162R2_std_visit_for_derived_classes_from_variant
tests\P2165R4_tuple_like_pair
tests\P2231R1_complete_constexpr_optional_variant
tests\P2273R3_constexpr_unique_ptr
tests\P2278R4_basic_const_iterator

Просмотреть файл

@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
RUNALL_INCLUDE ..\concepts_latest_matrix.lst

Просмотреть файл

@ -0,0 +1,189 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <array>
#include <cassert>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
using namespace std;
struct TmpInt {
constexpr TmpInt() : TmpInt(0) {}
constexpr TmpInt(int v) : val{v} {}
constexpr TmpInt(const TmpInt&) = default;
constexpr TmpInt(TmpInt&& other) : val{exchange(other.val, -1)} {}
constexpr TmpInt& operator=(const TmpInt&) = default;
constexpr TmpInt& operator=(TmpInt&& other) {
if (this != &other) {
val = exchange(other.val, -1);
}
return *this;
}
int val;
constexpr bool operator==(const TmpInt&) const = default;
};
template <template <class> class PairLike>
constexpr bool test_pair_like_constructor() {
// Test pair(pair-like) constructor with PairLike<int>&
static_assert(constructible_from<pair<int, int>, PairLike<int>&>);
static_assert(constructible_from<pair<int&, int&>, PairLike<int>&>);
static_assert(constructible_from<pair<const int&, const int&>, PairLike<int>&>);
static_assert(!constructible_from<pair<int&&, int&&>, PairLike<int>&>);
static_assert(!constructible_from<pair<const int&&, const int&&>, PairLike<int>&>);
// Test pair(pair-like) constructor with const PairLike<int>&
static_assert(constructible_from<pair<int, int>, const PairLike<int>&>);
static_assert(!constructible_from<pair<int&, int&>, const PairLike<int>&>);
static_assert(constructible_from<pair<const int&, const int&>, const PairLike<int>&>);
static_assert(!constructible_from<pair<int&&, int&&>, const PairLike<int>&>);
static_assert(!constructible_from<pair<const int&&, const int&&>, const PairLike<int>&>);
// Test pair(pair-like) constructor with PairLike<int>
static_assert(constructible_from<pair<int, int>, PairLike<int>>);
static_assert(!constructible_from<pair<int&, int&>, PairLike<int>>);
static_assert(constructible_from<pair<const int&, const int&>, PairLike<int>>);
static_assert(constructible_from<pair<int&&, int&&>, PairLike<int>>);
static_assert(constructible_from<pair<const int&&, const int&&>, PairLike<int>>);
// Test pair(pair-like) constructor with const PairLike<int>
static_assert(constructible_from<pair<int, int>, const PairLike<int>>);
static_assert(!constructible_from<pair<int&, int&>, const PairLike<int>>);
static_assert(constructible_from<pair<const int&, const int&>, const PairLike<int>>);
static_assert(!constructible_from<pair<int&&, int&&>, const PairLike<int>>);
static_assert(constructible_from<pair<const int&&, const int&&>, const PairLike<int>>);
PairLike<TmpInt> a = {1, 2};
pair<TmpInt, TmpInt> p1(a);
assert(p1.first == 1 && p1.second == 2 && get<0>(a) == 1 && get<1>(a) == 2);
pair<TmpInt&, TmpInt&> p2(a);
assert(&p2.first == &get<0>(a) && &p2.second == &get<1>(a));
pair<const TmpInt&, const TmpInt&> p3(a);
assert(&p3.first == &get<0>(a) && &p3.second == &get<1>(a));
pair<TmpInt, TmpInt> p4(as_const(a));
assert(p4.first == 1 && p4.second == 2 && get<0>(a) == 1 && get<1>(a) == 2);
pair<const TmpInt&, const TmpInt&> p5(as_const(a));
assert(&p5.first == &get<0>(a) && &p5.second == &get<1>(a));
pair<TmpInt, TmpInt> p6(std::move(a));
assert(p6.first == 1 && p6.second == 2 && get<0>(a) == -1 && get<1>(a) == -1);
tie(get<0>(a), get<1>(a)) = std::move(p6);
pair<const TmpInt&, const TmpInt&> p7(std::move(a));
assert(&p7.first == &get<0>(a) && &p7.second == &get<1>(a));
pair<TmpInt&&, TmpInt&&> p8(std::move(a));
assert(&p8.first == &get<0>(a) && &p8.second == &get<1>(a));
pair<const TmpInt&&, const TmpInt&&> p9(std::move(a));
assert(&p9.first == &get<0>(a) && &p9.second == &get<1>(a));
pair<TmpInt, TmpInt> p10(std::move(as_const(a)));
assert(p10.first == 1 && p10.second == 2 && get<0>(a) == 1 && get<1>(a) == 2);
pair<const TmpInt&, const TmpInt&> p11(std::move(as_const(a)));
assert(p11.first == 1 && p11.second == 2 && get<0>(a) == 1 && get<1>(a) == 2);
pair<const TmpInt&&, const TmpInt&&> p12(std::move(as_const(a)));
assert(p12.first == 1 && p12.second == 2 && get<0>(a) == 1 && get<1>(a) == 2);
return true;
}
template <template <class> class PairLike>
constexpr bool test_pair_like_assignment() {
static_assert(is_assignable_v<pair<int, int>&, PairLike<int>&>);
static_assert(is_assignable_v<pair<int, int>&, const PairLike<int>&>);
static_assert(is_assignable_v<pair<int, int>&, PairLike<int>>);
static_assert(is_assignable_v<pair<int, int>&, const PairLike<int>>);
PairLike<TmpInt> a = {1, 2};
pair<TmpInt, TmpInt> p1;
p1 = a;
assert(p1.first == 1 && p1.second == 2 && get<0>(a) == 1 && get<1>(a) == 2);
pair<TmpInt, TmpInt> p2;
p2 = as_const(a);
assert(p2.first == 1 && p2.second == 2 && get<0>(a) == 1 && get<1>(a) == 2);
pair<TmpInt, TmpInt> p3;
p3 = std::move(a);
assert(p3.first == 1 && p3.second == 2 && get<0>(a) == -1 && get<1>(a) == -1);
tie(get<0>(a), get<1>(a)) = std::move(p3);
pair<TmpInt, TmpInt> p4;
p4 = std::move(as_const(a));
assert(p4.first == 1 && p4.second == 2 && get<0>(a) == 1 && get<1>(a) == 2);
return true;
}
template <template <class> class PairLike>
constexpr bool test_pair_like_const_assignment() {
using Ref = vector<bool>::reference;
using Pair = pair<Ref, Ref>;
static_assert(is_assignable_v<const Pair&, PairLike<bool>&>);
static_assert(is_assignable_v<const Pair&, const PairLike<bool>&>);
static_assert(is_assignable_v<const Pair&, PairLike<bool>>);
static_assert(is_assignable_v<const Pair&, const PairLike<bool>>);
vector<bool> bools = {false, true};
PairLike<bool> a = {true, false};
const Pair p1{bools[0], bools[1]};
p1 = a;
assert(p1.first == true && p1.second == false);
bools = {false, true};
const Pair p2{bools[0], bools[1]};
p2 = as_const(a);
assert(p2.first == true && p2.second == false);
bools = {false, true};
const Pair p3{bools[0], bools[1]};
p3 = std::move(a);
assert(p3.first == true && p3.second == false);
bools = {false, true};
const Pair p4{bools[0], bools[1]};
p4 = std::move(as_const(a));
assert(p4.first == true && p4.second == false);
return true;
}
template <class T>
using BinaryTuple = tuple<T, T>;
template <class T>
using BinaryArray = array<T, 2>;
int main() {
static_assert(test_pair_like_constructor<BinaryArray>());
assert((test_pair_like_constructor<BinaryArray>()));
static_assert(test_pair_like_constructor<BinaryTuple>());
assert((test_pair_like_constructor<BinaryTuple>()));
static_assert(test_pair_like_assignment<BinaryArray>());
assert((test_pair_like_assignment<BinaryArray>()));
static_assert(test_pair_like_assignment<BinaryTuple>());
assert((test_pair_like_assignment<BinaryTuple>()));
static_assert(test_pair_like_const_assignment<BinaryArray>());
assert((test_pair_like_const_assignment<BinaryArray>()));
static_assert(test_pair_like_const_assignment<BinaryTuple>());
assert((test_pair_like_const_assignment<BinaryTuple>()));
}