From df834253730d0c332aa7fa45bfdd48f6ee1f6a7d Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 2 Jan 2023 17:35:49 -0700 Subject: [PATCH 01/41] Added the (untested) zip_transform_view implementation. --- stl/inc/ranges | 403 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 403 insertions(+) diff --git a/stl/inc/ranges b/stl/inc/ranges index ef960d5722..41daf649ac 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -7274,6 +7274,12 @@ namespace ranges { private: friend zip_view; + template + requires ((view<_Views> && ...) && (sizeof...(_Views) > 0) + && is_object_v<_Func> && regular_invocable<_Func&, range_reference_t<_Views>...> + && _Can_reference...>>) + friend class zip_transform_view; + using _My_tuple = tuple>...>; /* [[no_unique_address]] */ _My_tuple _Current; @@ -7656,6 +7662,403 @@ namespace ranges { _EXPORT_STD inline constexpr _Zip_fn zip{}; } // namespace views + _EXPORT_STD template + requires ((view<_ViewTypes> && ...) && (sizeof...(_ViewTypes) > 0) + && is_object_v<_Func> && regular_invocable<_Func&, range_reference_t<_ViewTypes>...> + && _Can_reference...>>) + class zip_transform_view : public view_interface> { + private: + using _Inner_view = zip_view<_ViewTypes...>; + + /* [[no_unique_address]] */ _Movable_box<_Func> _Function; + /* [[no_unique_address]] */ _Inner_view _Zip; + + template + using _Ziperator = iterator_t<_Maybe_const<_IsConst, _Inner_view>>; + + template + using _Zentinel = sentinel_t<_Maybe_const<_IsConst, _Inner_view>>; + + template + _NODISCARD static constexpr const auto& _Get_ziperator_tuple(const _Ziperator<_IsConst>& _Itr) noexcept { + return _Itr._Current; + } + + template + struct _Category_base {}; + + template + requires forward_range<_Maybe_const<_IsConst, _Inner_view>> + struct _Category_base<_IsConst> { + private: + enum class _Category_id { + _Random_access_iterator, + _Bidirectional_iterator, + _Forward_iterator, + _Input_iterator + }; + + static constexpr _Category_id _Curr_id = []() noexcept { + using _Invoke_t = invoke_result_t<_Maybe_const<_IsConst, _Func>&, + range_reference_t<_Maybe_const<_IsConst, _ViewTypes>>...>; + + if constexpr (!is_reference_v<_Invoke_t>) { + return _Category_id::_Input_iterator; + } else { + constexpr auto _Check_tag_type_closure = []() noexcept { + return (derived_from< + iterator_traits>>::iterator_category, + _Tag_type> + && ...); + }; + + if constexpr (_Check_tag_type_closure.template operator()()) { + return _Category_id::_Random_access_iterator; + } else if constexpr (_Check_tag_type_closure.template operator()()) { + return _Category_id::_Bidirectional_iterator; + } else if constexpr (_Check_tag_type_closure.template operator()()) { + return _Category_id::_Forward_iterator; + } else { + return _Category_id::_Input_iterator; + } + } + }(); + + public: + using iterator_category = + conditional_t<_Curr_id == _Category_id::_Random_access_iterator, random_access_iterator_tag, + conditional_t<_Curr_id == _Category_id::_Bidirectional_iterator, bidirectional_iterator_tag, + conditional_t<_Curr_id == _Category_id::_Forward_iterator, forward_iterator_tag, + input_iterator_tag>>>; + }; + + template + class _Iterator : public _Category_base<_IsConst> { + private: + friend zip_transform_view; + + using _Parent_t = _Maybe_const<_IsConst, zip_transform_view>; + using _Base_t = _Maybe_const<_IsConst, _Inner_view>; + + _Parent_t* _Parent = nullptr; + /* [[no_unique_address]] */ _Ziperator<_IsConst> _Inner; + + constexpr _Iterator(_Parent_t& _Parent_, _Ziperator<_IsConst> _Inner_) noexcept( + is_nothrow_move_constructible_v<_Ziperator<_IsConst>>) // strengthened + : _Parent(_STD addressof(_Parent_)), _Inner(_STD move(_Inner_)) {} + + public: + using iterator_concept = typename _Ziperator<_IsConst>::iterator_concept; + using value_type = remove_cvref_t&, + range_reference_t<_Maybe_const<_IsConst, _ViewTypes>>...>>; + using difference_type = range_difference_t<_Base_t>; + + _Iterator() = default; + + constexpr _Iterator(_Iterator _Rhs) noexcept( + is_nothrow_convertible_v<_Ziperator, _Ziperator<_IsConst>>) // strengthened + requires (_IsConst && convertible_to<_Ziperator, _Ziperator<_IsConst>>) + : _Parent(_Rhs._Parent), _Inner(_STD move(_Rhs._Inner)) {} + + _NODISCARD constexpr decltype(auto) operator*() const + noexcept(noexcept(_STD apply(_Dereference_closure(), _Get_ziperator_tuple<_IsConst>(_Inner)))) { + return _STD apply(_Dereference_closure(), _Get_ziperator_tuple<_IsConst>(_Inner)); + } + + constexpr _Iterator& operator++() noexcept(noexcept(++_Inner)) // strengthened + { + ++_Inner; + return *this; + } + + constexpr void operator++(int) noexcept(noexcept(++_STD declval<_Iterator&>())) // strengthened + { + ++*this; + } + + constexpr _Iterator operator++(int) noexcept( + is_nothrow_copy_constructible_v<_Iterator>&& noexcept(++*this)) // strengthened + requires forward_range<_Base_t> + { + auto _Temp = *this; + ++*this; + + return _Temp; + } + + constexpr _Iterator& operator--() noexcept(noexcept(--_Inner)) // strengthened + requires bidirectional_range<_Base_t> + { + --_Inner; + return *this; + } + + constexpr _Iterator operator--(int) noexcept( + is_nothrow_copy_constructible_v<_Iterator>&& noexcept(--*this)) // strengthened + requires bidirectional_range<_Base_t> + { + auto _Temp = *this; + --*this; + + return _Temp; + } + + constexpr _Iterator& operator+=(const difference_type _Off) noexcept( + noexcept(_Inner += _Off)) // strengthened + requires random_access_range<_Base_t> + { + _Inner += _Off; + return *this; + } + + constexpr _Iterator& operator-=(const difference_type _Off) noexcept( + noexcept(_Inner -= _Off)) // strengthened + requires random_access_range<_Base_t> + { + _Inner -= _Off; + return *this; + } + + _NODISCARD constexpr decltype(auto) operator[](const difference_type _Where) const noexcept(noexcept( + _STD apply(_Random_access_closure(_Where), _Get_ziperator_tuple<_IsConst>(_Inner)))) // strengthened + requires random_access_range<_Base_t> + { + return _STD apply(_Random_access_closure(_Where), _Get_ziperator_tuple<_IsConst>(_Inner)); + } + + _NODISCARD_FRIEND constexpr bool operator==(const _Iterator& _Lhs, const _Iterator& _Rhs) noexcept( + noexcept(_Lhs._Inner == _Rhs._Inner)) // strengthened + requires equality_comparable<_Ziperator<_IsConst>> + { + return _Lhs._Inner == _Rhs._Inner; + } + + _NODISCARD_FRIEND constexpr auto operator<=>(const _Iterator& _Lhs, const _Iterator& _Rhs) noexcept( + noexcept(_Lhs._Inner <=> _Rhs._Inner)) // strengthened + requires random_access_range<_Base_t> + { + return _Lhs._Inner <=> _Rhs._Inner; + } + + _NODISCARD_FRIEND constexpr _Iterator operator+(const _Iterator& _Lhs, const difference_type _Rhs) noexcept( + noexcept(_Iterator{*_Lhs._Parent, _Lhs._Inner + _Rhs})) // strengthened + requires random_access_range<_Base_t> + { + return _Iterator{*_Lhs._Parent, _Lhs._Inner + _Rhs}; + } + + _NODISCARD_FRIEND constexpr _Iterator operator+(const difference_type _Lhs, const _Iterator& _Rhs) noexcept( + noexcept(_Iterator{*_Rhs._Parent, _Rhs._Inner + _Lhs})) // strengthened + requires random_access_range<_Base_t> + { + return _Iterator{*_Rhs._Parent, _Rhs._Inner + _Lhs}; + } + + _NODISCARD_FRIEND constexpr _Iterator operator-(const _Iterator& _Lhs, const difference_type _Rhs) noexcept( + noexcept(_Iterator{*_Lhs._Parent, _Lhs._Inner - _Rhs})) // strengthened + requires random_access_range<_Base_t> + { + return _Iterator{*_Lhs._Parent, _Lhs._Inner - _Rhs}; + } + + // clang-format off + _NODISCARD_FRIEND constexpr difference_type operator-(const _Iterator& _Lhs, const _Iterator& _Rhs) noexcept( + noexcept(_Lhs._Inner - _Rhs._Inner)) // strengthened + requires sized_sentinel_for<_Ziperator<_IsConst>, _Ziperator<_IsConst>> + { + // clang-format on + return _Lhs._Inner - _Rhs._Inner; + } + + private: + _NODISCARD constexpr auto _Dereference_closure() const noexcept { + return [this](const auto&... _Itrs) noexcept(noexcept(_STD invoke(*_Parent->_Function, + *_Itrs...))) -> decltype(auto) { return _STD invoke(*_Parent->_Function, *_Itrs...); }; + } + + _NODISCARD constexpr auto _Random_access_closure(const difference_type _Where) const noexcept { + return [this, _Where](const _Iterator_types&... _Iters) noexcept( + noexcept(_STD invoke(*_Parent->_Function, + _Iters[static_cast>(_Where)]...))) -> decltype(auto) { + return _STD invoke( + *_Parent->_Function, _Iters[static_cast>(_Where)]...); + }; + } + }; + + template + class _Sentinel { + private: + friend zip_transform_view; + + /* [[no_unique_address]] */ _Zentinel<_IsConst> _Inner; + + constexpr explicit _Sentinel(_Zentinel<_IsConst> _Inner_) noexcept( + is_nothrow_copy_constructible_v<_Zentinel<_IsConst>>) // strengthened + : _Inner(_Inner_) {} + + template + _NODISCARD static constexpr const auto& _Get_iterator_ziperator( + const _Iterator<_IteratorConst>& _Itr) noexcept { + return _Itr._Inner; + } + + public: + _Sentinel() = default; + + constexpr _Sentinel(_Sentinel _Rhs) noexcept( + is_nothrow_convertible_v<_Zentinel, _Zentinel<_IsConst>>) // strengthened + requires (_IsConst && convertible_to<_Zentinel, _Zentinel<_IsConst>>) + : _Inner(_STD move(_Rhs._Inner)) {} + + template + requires sentinel_for<_Zentinel<_IsConst>, _Ziperator<_IteratorConst>> + // clang-format off + _NODISCARD_FRIEND constexpr bool operator==(const _Iterator<_IteratorConst>& _Lhs, const _Sentinel& _Rhs) + noexcept(noexcept(_Get_iterator_ziperator(_Lhs) == _Rhs._Inner)) // strengthened + { + // clang-format on + return _Get_iterator_ziperator(_Lhs) == _Rhs._Inner; + } + + template + requires sized_sentinel_for<_Zentinel<_IsConst>, _Ziperator<_IteratorConst>> + _NODISCARD_FRIEND constexpr range_difference_t<_Maybe_const<_IteratorConst, _Inner_view>> + // clang-format off + operator-(const _Iterator<_IteratorConst>& _Lhs, const _Sentinel& _Rhs) + noexcept(noexcept(_Get_iterator_ziperator(_Lhs) - _Rhs._Inner)) // strengthened + { + // clang-format on + return _Get_iterator_ziperator(_Lhs) - _Rhs._Inner; + } + + template + requires sized_sentinel_for<_Zentinel<_IsConst>, _Ziperator<_IteratorConst>> + _NODISCARD_FRIEND constexpr range_difference_t<_Maybe_const<_IteratorConst, _Inner_view>> + // clang-format off + operator-(const _Sentinel& _Lhs, const _Iterator<_IteratorConst>& _Rhs) + noexcept(noexcept(_Lhs._Inner - _Get_iterator_ziperator(_Rhs))) // strengthened + { + // clang-format on + return _Lhs._Inner - _Get_iterator_ziperator(_Rhs); + } + }; + + template + static constexpr bool _Is_end_noexcept = []() noexcept { + if constexpr (common_range<_Maybe_const<_IsConst, _Inner_view>>) { + return noexcept(_Iterator<_IsConst>{_STD declval<_Maybe_const<_IsConst, zip_transform_view>&>(), + _STD declval<_Maybe_const<_IsConst, _Inner_view>&>().end()}); + } else { + return noexcept(_Sentinel<_IsConst>{_STD declval<_Maybe_const<_IsConst, _Inner_view>&>().end()}); + } + }(); + + static constexpr bool _Enable_const_begin_end = + (range && regular_invocable...>); + + public: + zip_transform_view() = default; + + constexpr explicit zip_transform_view(_Func _Function_, _ViewTypes... _Views) noexcept( + is_nothrow_move_constructible_v<_Movable_box<_Func>>&& + is_nothrow_constructible_v<_Inner_view, remove_reference_t<_ViewTypes>&&...>) // strengthened + : _Function(in_place, _STD move(_Function_)), _Zip(_STD move(_Views)...) {} + + constexpr auto begin() noexcept(noexcept(_Iterator{*this, _Zip.begin()})) // strengthened + { + return _Iterator{*this, _Zip.begin()}; + } + + constexpr auto begin() const noexcept(noexcept(_Iterator{*this, _Zip.begin()})) // strengthened + requires _Enable_const_begin_end + { + return _Iterator{*this, _Zip.begin()}; + } + + constexpr auto end() noexcept(_Is_end_noexcept) // strengthened + { + if constexpr (common_range<_Inner_view>) { + return _Iterator{*this, _Zip.end()}; + } else { + return _Sentinel{_Zip.end()}; + } + } + + constexpr auto end() const noexcept(_Is_end_noexcept) // strengthened + requires _Enable_const_begin_end + { + if constexpr (common_range) { + return _Iterator{*this, _Zip.end()}; + } else { + return _Sentinel{_Zip.end()}; + } + } + + constexpr auto size() noexcept(noexcept(_Zip.size())) // strengthened + requires sized_range<_Inner_view> + { + return _Zip.size(); + } + + constexpr auto size() const noexcept(noexcept(_Zip.size())) // strengthened + requires sized_range + { + return _Zip.size(); + } + }; + + template + zip_transform_view(_Func, _Ranges&&...) -> zip_transform_view<_Func, views::all_t<_Ranges>...>; + + namespace views { + struct _Zip_transform_fn { + private: + template + static constexpr bool _Is_invocation_noexcept = []() noexcept { + if constexpr (sizeof...(_Types) == 0) { + using _Decayed_func = decay_t<_Func>; + if constexpr (move_constructible<_Decayed_func> && regular_invocable<_Decayed_func> + && is_object_v>>) { + // NOTE: As of writing this, the MSVC hasn't implemented auto(x)/auto{x} + // (see P0849R8). We need to thus manually describe the type instead of using + // auto(std::views::empty>>) + // as the C++ standard proposes. + // + // (The alternative is to use the exposition-only function decay-copy, but this + // isn't what's written in the standard anymore.) + using _Empty_view_t = decltype(empty>>); + return noexcept(((void) _STD declval<_Func&>(), + _Empty_view_t{empty>>})); + } else { + return false; + } + } else { + return noexcept(noexcept(zip_transform_view(_STD declval<_Func>(), _STD declval<_Types>()...))); + } + }(); + + public: + template + _NODISCARD constexpr auto operator()(_Func&& _Function, _Types&&... _Args) const + noexcept(_Is_invocation_noexcept<_Func, _Types...>) // strengthened + { + if constexpr (sizeof...(_Types) == 0) { + using _Decayed_func = decay_t; + static_assert(move_constructible<_Decayed_func> && regular_invocable<_Decayed_func> + && is_object_v>>); + + using _Empty_view_t = decltype(empty>>); + return ((void) _Function, _Empty_view_t{empty>>}); + } else { + return zip_transform_view(_STD forward<_Func>(_Function), _STD forward<_Types>(_Args)...); + } + } + }; + + _EXPORT_STD inline constexpr _Zip_transform_fn zip_transform{}; + } // namespace views + #ifdef __cpp_lib_ranges_to_container // clang-format off template From b1e6febcf7842128b075bede45dcb5c60b367803 Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 3 Jan 2023 07:55:17 -0700 Subject: [PATCH 02/41] Removed an unnecessary static_assert. --- stl/inc/ranges | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/stl/inc/ranges b/stl/inc/ranges index 41daf649ac..192dcdbe93 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -8018,21 +8018,18 @@ namespace ranges { static constexpr bool _Is_invocation_noexcept = []() noexcept { if constexpr (sizeof...(_Types) == 0) { using _Decayed_func = decay_t<_Func>; - if constexpr (move_constructible<_Decayed_func> && regular_invocable<_Decayed_func> - && is_object_v>>) { - // NOTE: As of writing this, the MSVC hasn't implemented auto(x)/auto{x} - // (see P0849R8). We need to thus manually describe the type instead of using - // auto(std::views::empty>>) - // as the C++ standard proposes. - // - // (The alternative is to use the exposition-only function decay-copy, but this - // isn't what's written in the standard anymore.) - using _Empty_view_t = decltype(empty>>); - return noexcept(((void) _STD declval<_Func&>(), - _Empty_view_t{empty>>})); - } else { - return false; - } + + // NOTE: As of writing this, the MSVC hasn't implemented auto(x)/auto{x} + // (see P0849R8). We need to thus manually describe the type instead of using + // auto(std::views::empty>>) + // as the C++ standard proposes. + // + // (The alternative is to use the exposition-only function decay-copy, but this + // isn't what's written in the standard anymore.) + using _Empty_view_t = decltype(empty>>); + + return noexcept(( + (void) _STD declval<_Func&>(), _Empty_view_t{empty>>})); } else { return noexcept(noexcept(zip_transform_view(_STD declval<_Func>(), _STD declval<_Types>()...))); } @@ -8045,10 +8042,8 @@ namespace ranges { { if constexpr (sizeof...(_Types) == 0) { using _Decayed_func = decay_t; - static_assert(move_constructible<_Decayed_func> && regular_invocable<_Decayed_func> - && is_object_v>>); - using _Empty_view_t = decltype(empty>>); + return ((void) _Function, _Empty_view_t{empty>>}); } else { return zip_transform_view(_STD forward<_Func>(_Function), _STD forward<_Types>(_Args)...); From d7aa531cac04e6575aa2ebaadf26c62dc2ff714b Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 3 Jan 2023 08:15:08 -0700 Subject: [PATCH 03/41] Updated `yvals_core.h` for `std::views::transform`. --- stl/inc/yvals_core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 541037dc0c..289d832377 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -330,7 +330,7 @@ // P2291R3 constexpr Integral // P2302R4 ranges::contains, ranges::contains_subrange // P2321R2 zip -// (missing views::zip_transform, views::adjacent, and views::adjacent_transform) +// (missing views::adjacent and views::adjacent_transform) // P2322R6 ranges::fold_left, ranges::fold_right, Etc. // P2387R3 Pipe Support For User-Defined Range Adaptors // P2417R2 More constexpr bitset From 9c84f60cd12baabf288a4222518d3a8f4cc247e0 Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 3 Jan 2023 08:34:18 -0700 Subject: [PATCH 04/41] Removed an unneeded accessor function. --- stl/inc/ranges | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/stl/inc/ranges b/stl/inc/ranges index 192dcdbe93..95da25c84f 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -7679,11 +7679,6 @@ namespace ranges { template using _Zentinel = sentinel_t<_Maybe_const<_IsConst, _Inner_view>>; - template - _NODISCARD static constexpr const auto& _Get_ziperator_tuple(const _Ziperator<_IsConst>& _Itr) noexcept { - return _Itr._Current; - } - template struct _Category_base {}; @@ -7706,8 +7701,8 @@ namespace ranges { return _Category_id::_Input_iterator; } else { constexpr auto _Check_tag_type_closure = []() noexcept { - return (derived_from< - iterator_traits>>::iterator_category, + return (derived_from>>::iterator_category, _Tag_type> && ...); }; @@ -7761,8 +7756,8 @@ namespace ranges { : _Parent(_Rhs._Parent), _Inner(_STD move(_Rhs._Inner)) {} _NODISCARD constexpr decltype(auto) operator*() const - noexcept(noexcept(_STD apply(_Dereference_closure(), _Get_ziperator_tuple<_IsConst>(_Inner)))) { - return _STD apply(_Dereference_closure(), _Get_ziperator_tuple<_IsConst>(_Inner)); + noexcept(noexcept(_STD apply(_Dereference_closure(), _Inner._Current))) { + return _STD apply(_Dereference_closure(), _Inner._Current); } constexpr _Iterator& operator++() noexcept(noexcept(++_Inner)) // strengthened @@ -7819,11 +7814,11 @@ namespace ranges { return *this; } - _NODISCARD constexpr decltype(auto) operator[](const difference_type _Where) const noexcept(noexcept( - _STD apply(_Random_access_closure(_Where), _Get_ziperator_tuple<_IsConst>(_Inner)))) // strengthened + _NODISCARD constexpr decltype(auto) operator[](const difference_type _Where) const + noexcept(noexcept(_STD apply(_Random_access_closure(_Where), _Inner._Current))) // strengthened requires random_access_range<_Base_t> { - return _STD apply(_Random_access_closure(_Where), _Get_ziperator_tuple<_IsConst>(_Inner)); + return _STD apply(_Random_access_closure(_Where), _Inner._Current); } _NODISCARD_FRIEND constexpr bool operator==(const _Iterator& _Lhs, const _Iterator& _Rhs) noexcept( From 9ab35fdd2a23a5a4bdf2fcd11f6cbedf367bacbe Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 3 Jan 2023 08:46:50 -0700 Subject: [PATCH 05/41] Improved some formatting. --- stl/inc/ranges | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/stl/inc/ranges b/stl/inc/ranges index 95da25c84f..4eef1c38b7 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -7701,10 +7701,12 @@ namespace ranges { return _Category_id::_Input_iterator; } else { constexpr auto _Check_tag_type_closure = []() noexcept { - return (derived_from>>::iterator_category, - _Tag_type> - && ...); + // clang-format off + return (derived_from>>::iterator_category, + _Tag_type> + && ...); + // clang-format on }; if constexpr (_Check_tag_type_closure.template operator()()) { @@ -7857,8 +7859,8 @@ namespace ranges { } // clang-format off - _NODISCARD_FRIEND constexpr difference_type operator-(const _Iterator& _Lhs, const _Iterator& _Rhs) noexcept( - noexcept(_Lhs._Inner - _Rhs._Inner)) // strengthened + _NODISCARD_FRIEND constexpr difference_type operator-(const _Iterator& _Lhs, const _Iterator& _Rhs) + noexcept(noexcept(_Lhs._Inner - _Rhs._Inner)) // strengthened requires sized_sentinel_for<_Ziperator<_IsConst>, _Ziperator<_IsConst>> { // clang-format on From ab1e99cf9bc5af2b0de625225d434c53f299a2a8 Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 3 Jan 2023 09:33:07 -0700 Subject: [PATCH 06/41] Re-implemented the static_asserts for LWG-3773. --- stl/inc/ranges | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/stl/inc/ranges b/stl/inc/ranges index 4eef1c38b7..c53deaed96 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -8016,17 +8016,34 @@ namespace ranges { if constexpr (sizeof...(_Types) == 0) { using _Decayed_func = decay_t<_Func>; - // NOTE: As of writing this, the MSVC hasn't implemented auto(x)/auto{x} - // (see P0849R8). We need to thus manually describe the type instead of using - // auto(std::views::empty>>) - // as the C++ standard proposes. - // - // (The alternative is to use the exposition-only function decay-copy, but this - // isn't what's written in the standard anymore.) - using _Empty_view_t = decltype(empty>>); - - return noexcept(( - (void) _STD declval<_Func&>(), _Empty_view_t{empty>>})); + // We do the static_asserts here, rather than in the definition of operator(), so that they + // show up earlier in compiler diagnostics. + static_assert(move_constructible<_Decayed_func>, + "ERROR: When using std::views::zip_transform without any views, the specified callable must " + "be move constructible (see [range.zip.transform.overview]/2.1.1)."); + + if constexpr (regular_invocable<_Decayed_func&>) { + static_assert(is_object_v>>, + "ERROR: When using std::views::zip_transform without any views, the specified callable " + "must return an object (see [range.zip.transform.overview]/2.1.1)."); + + // NOTE: As of writing this, the MSVC hasn't implemented auto(x)/auto{x} + // (see P0849R8). We need to thus manually describe the type instead of using + // auto(std::views::empty>>) + // as the C++ standard proposes. + // + // (The alternative is to use the exposition-only function decay-copy, but this + // isn't what's written in the standard anymore.) + using _Empty_view_t = decltype(empty>>); + + return noexcept(((void) _STD declval<_Func&>(), + _Empty_view_t{empty>>})); + } else { + static_assert(_Always_false<_Decayed_func>, + "ERROR: When using std::views::zip_transform without any views, the specified callable " + "must be invocable without any parameters (see [range.zip.transform.overview]/2.1.1)."); + return false; + } } else { return noexcept(noexcept(zip_transform_view(_STD declval<_Func>(), _STD declval<_Types>()...))); } From d625a914c1700146e2f3b01efab592668eeca06d Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 3 Jan 2023 15:27:54 -0700 Subject: [PATCH 07/41] Began work on the test suite. --- .../P2321R2_views_zip_transform/test.cpp | 397 ++++++++++++++++++ 1 file changed, 397 insertions(+) create mode 100644 tests/std/tests/P2321R2_views_zip_transform/test.cpp diff --git a/tests/std/tests/P2321R2_views_zip_transform/test.cpp b/tests/std/tests/P2321R2_views_zip_transform/test.cpp new file mode 100644 index 0000000000..5d70e946c7 --- /dev/null +++ b/tests/std/tests/P2321R2_views_zip_transform/test.cpp @@ -0,0 +1,397 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +#include + +using namespace std; + +template +class MoveDeleter { +public: + MoveDeleter() = default; +}; + +template <> +class MoveDeleter { +public: + MoveDeleter() = default; + + MoveDeleter(const MoveDeleter& rhs) = default; + MoveDeleter& operator=(const MoveDeleter& rhs) = default; + + MoveDeleter(MoveDeleter&& rhs) noexcept = delete; + MoveDeleter& operator=(MoveDeleter&& rhs) noexcept = delete; +}; + +template +class TestCallback : public MoveDeleter { +public: + TestCallback() = default; + + ValueType operator()(const ValueType& lhs, const ValueType& rhs) const { + return (lhs + rhs); + } +}; + +template +class TestCallback : public MoveDeleter { +public: + TestCallback() = default; + + void operator()() const {} +}; + +template +concept CanZipTransform = + requires(Function&& function, RangeTypes&&... ranges) { + views::zip_transform(std::forward(function), std::forward(ranges)...); + }; + +template +using AllView = views::all_t; + +template +using TransformResultType = invoke_result_t&, + ranges::range_reference_t>>...>; + +template +constexpr auto& maybe_as_const(T& value) { + if constexpr (IsConst) { + return as_const(value); + } else { + return value; + } +} + +template +constexpr bool test_one( + TransformType_&& transform, const TransformedElementsContainer& transformed_elements, RangeTypes&&... ranges) { + using TransformType = remove_cvref_t; + using ZipTransformType = ranges::zip_transform_view...>; + + constexpr bool are_views = (ranges::view> && ...) && (sizeof...(RangeTypes) > 0); + constexpr bool is_transform = + is_object_v + && regular_invocable>...> + && _Can_reference>...>>; + + STATIC_ASSERT(ranges::view); + + // Validate iterator concept + { + constexpr auto check_iterator_concept_closure = []() { + using transform_result_t = TransformResultType; + + if constexpr (!is_reference_v) { + STATIC_ASSERT(ranges::input_range); + STATIC_ASSERT(!ranges::forward_range); + STATIC_ASSERT(!ranges::bidirectional_range); + STATIC_ASSERT(!ranges::random_access_range); + } else { + constexpr auto check_iterator_tags_closure = []() { + return ( + derived_from>>>::iterator_category, + TagType> + && ...); + }; + + STATIC_ASSERT(ranges::input_range); + STATIC_ASSERT(ranges::forward_range + == check_iterator_tags_closure.template operator()()); + STATIC_ASSERT(ranges::bidirectional_range + == check_iterator_tags_closure.template operator()()); + STATIC_ASSERT(ranges::random_access_range + == check_iterator_tags_closure.template operator()()); + } + }; + + check_iterator_concept_closure.template operator()(); + check_iterator_concept_closure.template operator()(); + } + + using InnerView = ranges::zip_view...>; + + // Validate commonality + STATIC_ASSERT(ranges::common_range == ranges::common_range); + + if constexpr (should_have_const_begin_end) { + STATIC_ASSERT(ranges::common_range == ranges::common_range); + } + + // Validate conditional default-initializability + STATIC_ASSERT( + is_default_constructible_v == is_default_constructible_v> + && is_default_constructible_v); + STATIC_ASSERT(is_nothrow_default_constructible_v + == is_nothrow_default_constructible_v> + && is_nothrow_default_constructible_v); + + // Validate range adaptor object + { + constexpr bool can_copy_construct_ranges = (!are_views || (copy_constructible> && ...)); + constexpr bool can_move_ranges = (are_views || (movable> && ...)); + + // ... with lvalue arguments + STATIC_ASSERT(CanZipTransform == can_copy_construct_ranges + && move_constructible); + if constexpr (CanZipTransform) { + using ExpectedZipTransformType = ZipTransformType; + constexpr bool is_noexcept = is_nothrow_move_constructible_v> + && is_nothrow_constructible_v; + + STATIC_ASSERT(same_as(transform), ranges...)), + ExpectedZipTransformType>); + STATIC_ASSERT( + noexcept(views::zip_transform(std::forward(transform), ranges...)) == is_noexcept); + } + + // ... with const lvalue arguments + STATIC_ASSERT( + CanZipTransform&...> == can_copy_construct_ranges + && move_constructible); + if constexpr (CanZipTransform&...>) { + using ExpectedZipTransformType = + ranges::zip_transform_view&>...>; + constexpr bool is_noexcept = + is_nothrow_move_constructible_v> + && is_nothrow_constructible_v&>...>; + + STATIC_ASSERT( + same_as(transform), as_const(ranges)...)), + ExpectedZipTransformType>); + STATIC_ASSERT(noexcept(views::zip_transform(std::forward(transform), as_const(ranges)...)) + == is_noexcept); + } + + // ... with rvalue arguments + STATIC_ASSERT(CanZipTransform> == can_move_ranges + && move_constructible); + if constexpr (CanZipTransform...>) { + using ExpectedZipTransformType = + ranges::zip_transform_view>...>; + constexpr bool is_noexcept = + is_nothrow_move_constructible_v> + && is_nothrow_constructible_v>...>; + + STATIC_ASSERT( + same_as(transform), std::move(ranges)...)), + ExpectedZipTransformType>); + STATIC_ASSERT(noexcept(views::zip_transform(std::forward(transform), std::move(ranges)...)) + == is_noexcept); + } + + // ... with const rvalue arguments + STATIC_ASSERT(CanZipTransform> == can_copy_construct_ranges + && move_constructible); + if constexpr (CanZipTransform>) { + using ExpectedZipTransformType = + ranges::zip_transform_view>...>; + constexpr bool is_noexcept = + is_nothrow_move_constructible_v> + && is_nothrow_constructible_v>...>; + + STATIC_ASSERT(same_as(transform), std::move(as_const(ranges))...)), + ExpectedZipTransformType>); + STATIC_ASSERT( + noexcept(views::zip_transform(std::forward(transform), std::move(as_const(ranges))...)) + == is_noexcept); + } + } + + if constexpr (move_constructible) { + // Validate deduction guide + same_as auto zipped_transformed_range = + ranges::zip_transform_view{std::forward(transform), std::forward(ranges)...}; + + // Validate zip_transform_view::size() + STATIC_ASSERT(CanMemberSize == ranges::sized_range); + if constexpr (CanMemberSize) { + using expected_size_type = decltype(declval().size()); + same_as auto zip_transform_size = zipped_transformed_range.size(); + + assert(zip_transform_size == ranges::size(transformed_elements)); + STATIC_ASSERT(noexcept(zipped_transformed_range.size()) == noexcept(declval().size())); + } + + STATIC_ASSERT(CanMemberSize == ranges::sized_range); + if constexpr (CanMemberSize) { + using expected_size_type = decltype(declval().size()); + same_as auto zip_transform_size = as_const(zipped_transformed_range).size(); + + assert(zip_transform_size == ranges::size(transformed_elements)); + STATIC_ASSERT(noexcept(zipped_transformed_range.size()) == noexcept(declval().size())); + } + + const bool is_empty = ranges::empty(transformed_elements); + + // We don't want an empty results range, since we still need to do additional testing. + assert(!is_empty); + + // Validate view_interface::empty() and view_interface::operator bool + // + // From here on out, we'll be re-using concepts which we already verified to reduce + // redundancy. + STATIC_ASSERT(CanMemberEmpty == ranges::sized_range + || ranges::forward_range); + if constexpr (CanMemberEmpty) { + assert(zipped_transformed_range.empty() == is_empty); + } + + STATIC_ASSERT(CanMemberEmpty == ranges::sized_range + || ranges::forward_range); + if constexpr (CanMemberEmpty) { + assert(as_const(zipped_transformed_range).empty() == is_empty); + } + + STATIC_ASSERT(CanBool == CanMemberEmpty); + if constexpr (CanBool) { + assert(static_cast(zipped_transformed_range) != is_empty); + } + + STATIC_ASSERT(CanBool == CanMemberEmpty); + if constexpr (CanBool) { + assert(static_cast(as_const(zipped_transformed_range)) != is_empty); + } + + // Validate contents of zip-transformed range + assert(ranges::equal(zipped_transformed_range, transformed_elements)); + +#pragma warning(push) +#pragma warning(disable : 4127) // Conditional Expression is Constant + if (!(ranges::forward_range> && ...)) // intentionally not if constexpr + { + return false; + } +#pragma warning(pop) + + // Validate view_interface::data() + // + // This should never exist because zip_transform_view does not model + // std::contiguous_range. + STATIC_ASSERT(!ranges::contiguous_range); + STATIC_ASSERT(!CanMemberData); + STATIC_ASSERT(!ranges::contiguous_range); + STATIC_ASSERT(!CanMemberData); + + // Validate view_interface::front() + { + constexpr auto validate_front_closure = [&]() { + STATIC_ASSERT(CanMemberFront> + == ranges::forward_range>); + if constexpr (CanMemberFront>) { + using transform_result_t = TransformResultType; + same_as auto first_result = + maybe_as_const(zipped_transformed_range).front(); + + assert(first_result == *ranges::begin(transformed_elements)); + } + }; + + validate_front_closure.template operator()(); + validate_front_closure.template operator()(); + } + + // Validate view_interface::back() + { + constexpr auto validate_back_closure = [&]() { + STATIC_ASSERT(CanMemberBack> + == ranges::bidirectional_range> + && ranges::common_range>); + if constexpr (CanMemberBack>) { + using transform_result_t = TransformResultType; + same_as auto last_result = + maybe_as_const(zipped_transformed_range).back(); + + assert(last_result == *ranges::prev(ranges::end(transformed_elements))); + } + }; + + validate_back_closure.template operator()(); + validate_back_closure.template operator()(); + } + + // Validate view_interface::operator[] + { + constexpr auto validate_random_access_closure = [&]() { + STATIC_ASSERT(CanIndex> + == ranges::random_access_range>); + if constexpr (CanIndex>) { + using transform_result_t = TransformResultType; + same_as auto first_result = + maybe_as_const(zipped_transformed_range)[0]; + + assert(first_result == ranges::begin(transformed_elements)[0]); + } + }; + + validate_random_access_closure.template operator()(); + validate_random_access_closure.template operator()(); + } + + // Validate zip_transform_view::begin() and zip_transform_view::end() + STATIC_ASSERT(CanMemberBegin); + STATIC_ASSERT(CanMemberEnd) { + same_as> auto begin_itr = zipped_transformed_range.begin(); + same_as> auto end_sentinel = zipped_transformed_range.end(); + + assert(*begin_itr == *ranges::begin(transformed_elements)); + assert(ranges::distance(begin_itr, end_sentinel) == ranges::size(transformed_elements)); + STATIC_ASSERT(noexcept(zipped_transformed_range.begin()) + == is_nothrow_constructible_v, ZipTransformType&, + ranges::iterator_t>); + + if constexpr (ranges::common_range) { + STATIC_ASSERT(noexcept(zipped_transformed_range.end()) + == is_nothrow_constructible_v, ZipTransformType&, + ranges::iterator_t>); + } else { + STATIC_ASSERT( + noexcept(zipped_transformed_range.end()) + == is_nothrow_constructible_v, ranges::sentinel_t>); + } + } + + constexpr bool has_const_begin_end = + ranges::range + && regular_invocable...>; + + STATIC_ASSERT(CanMemberBegin == has_const_begin_end); + STATIC_ASSERT(CanMemberEnd == has_const_begin_end); + if constexpr (has_const_begin_end) { + same_as> auto begin_itr = + as_const(zipped_transformed_range).begin(); + same_as> auto end_sentinel = + as_const(zipped_transformed_range).end(); + + assert(*begin_itr == *ranges::begin(transformed_elements)); + assert(ranges::distance(begin_itr, end_sentinel) == ranges::size(transformed_elements)); + STATIC_ASSERT(noexcept(as_const(zipped_transformed_range).begin()) + == is_nothrow_constructible_v, + const ZipTransformType&, ranges::iterator_t>); + + if constexpr (ranges::common_range) { + STATIC_ASSERT(noexcept(as_const(zipped_transformed_range).end()) + == is_nothrow_constructible_v, + const ZipTransformType&, ranges::iterator_t>); + } else { + STATIC_ASSERT(noexcept(as_const(zipped_transformed_range).end()) + == is_nothrow_constructible_v, + ranges::sentinel_t>); + } + } + + // TODO: Add testing for iterators and sentinels returned by zip_transform_view! + } + + return true; +} + +constexpr auto transform_closure = []( + const Type1& a, const Type2& b, const Type3& c) { return (a * b + c); }; \ No newline at end of file From 2271c17d687677c68ca0ada4e38ac687df6436f8 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 4 Jan 2023 17:26:38 -0700 Subject: [PATCH 08/41] Added the test suite. (TODO: Work around Clang crash.) --- stl/inc/ranges | 21 +- .../tests/P2321R2_views_zip_transform/env.lst | 9 + .../P2321R2_views_zip_transform/test.cpp | 1057 ++++++++++++----- 3 files changed, 768 insertions(+), 319 deletions(-) create mode 100644 tests/std/tests/P2321R2_views_zip_transform/env.lst diff --git a/stl/inc/ranges b/stl/inc/ranges index c53deaed96..55b0e71635 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -7750,7 +7750,7 @@ namespace ranges { range_reference_t<_Maybe_const<_IsConst, _ViewTypes>>...>>; using difference_type = range_difference_t<_Base_t>; - _Iterator() = default; + _Iterator() noexcept(is_nothrow_default_constructible_v<_Ziperator<_IsConst>>) = default; constexpr _Iterator(_Iterator _Rhs) noexcept( is_nothrow_convertible_v<_Ziperator, _Ziperator<_IsConst>>) // strengthened @@ -7888,6 +7888,13 @@ namespace ranges { private: friend zip_transform_view; + // So, here's an interesting MSVC bug: If we have _Sentinel::operator-() return + // range_difference_t<_Maybe_const<_IteratorConst, _Inner_view>> directly as suggested + // in the C++ standard, ADL does not seem to find the function in the test suite, and the + // program fails to compile. This does not seem to happen with Clang. + template + using _Iterator_sentinel_difference_type = range_difference_t<_Maybe_const<_IteratorConst, _Inner_view>>; + /* [[no_unique_address]] */ _Zentinel<_IsConst> _Inner; constexpr explicit _Sentinel(_Zentinel<_IsConst> _Inner_) noexcept( @@ -7901,7 +7908,7 @@ namespace ranges { } public: - _Sentinel() = default; + _Sentinel() noexcept(is_nothrow_default_constructible_v<_Zentinel<_IsConst>>) = default; constexpr _Sentinel(_Sentinel _Rhs) noexcept( is_nothrow_convertible_v<_Zentinel, _Zentinel<_IsConst>>) // strengthened @@ -7920,7 +7927,7 @@ namespace ranges { template requires sized_sentinel_for<_Zentinel<_IsConst>, _Ziperator<_IteratorConst>> - _NODISCARD_FRIEND constexpr range_difference_t<_Maybe_const<_IteratorConst, _Inner_view>> + _NODISCARD_FRIEND constexpr _Iterator_sentinel_difference_type<_IteratorConst> // clang-format off operator-(const _Iterator<_IteratorConst>& _Lhs, const _Sentinel& _Rhs) noexcept(noexcept(_Get_iterator_ziperator(_Lhs) - _Rhs._Inner)) // strengthened @@ -7931,7 +7938,7 @@ namespace ranges { template requires sized_sentinel_for<_Zentinel<_IsConst>, _Ziperator<_IteratorConst>> - _NODISCARD_FRIEND constexpr range_difference_t<_Maybe_const<_IteratorConst, _Inner_view>> + _NODISCARD_FRIEND constexpr _Iterator_sentinel_difference_type<_IteratorConst> // clang-format off operator-(const _Sentinel& _Lhs, const _Iterator<_IteratorConst>& _Rhs) noexcept(noexcept(_Lhs._Inner - _Get_iterator_ziperator(_Rhs))) // strengthened @@ -7955,7 +7962,9 @@ namespace ranges { (range && regular_invocable...>); public: - zip_transform_view() = default; + zip_transform_view() noexcept( + is_nothrow_default_constructible_v<_Movable_box<_Func>>&& is_nothrow_default_constructible_v<_Inner_view>) = + default; constexpr explicit zip_transform_view(_Func _Function_, _ViewTypes... _Views) noexcept( is_nothrow_move_constructible_v<_Movable_box<_Func>>&& @@ -8045,7 +8054,7 @@ namespace ranges { return false; } } else { - return noexcept(noexcept(zip_transform_view(_STD declval<_Func>(), _STD declval<_Types>()...))); + return noexcept(zip_transform_view(_STD declval<_Func>(), _STD declval<_Types>()...)); } }(); diff --git a/tests/std/tests/P2321R2_views_zip_transform/env.lst b/tests/std/tests/P2321R2_views_zip_transform/env.lst new file mode 100644 index 0000000000..163883a1d4 --- /dev/null +++ b/tests/std/tests/P2321R2_views_zip_transform/env.lst @@ -0,0 +1,9 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\strict_concepts_latest_matrix.lst +RUNALL_CROSSLIST +PM_CL="/DTEST_INPUT" +PM_CL="/DTEST_FORWARD" +PM_CL="/DTEST_BIDIRECTIONAL" +PM_CL="/DTEST_RANDOM" diff --git a/tests/std/tests/P2321R2_views_zip_transform/test.cpp b/tests/std/tests/P2321R2_views_zip_transform/test.cpp index 5d70e946c7..cd03f2b623 100644 --- a/tests/std/tests/P2321R2_views_zip_transform/test.cpp +++ b/tests/std/tests/P2321R2_views_zip_transform/test.cpp @@ -2,61 +2,24 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include +#include +#include +#include #include +#include +#include #include #include using namespace std; -template -class MoveDeleter { -public: - MoveDeleter() = default; -}; - -template <> -class MoveDeleter { -public: - MoveDeleter() = default; - - MoveDeleter(const MoveDeleter& rhs) = default; - MoveDeleter& operator=(const MoveDeleter& rhs) = default; - - MoveDeleter(MoveDeleter&& rhs) noexcept = delete; - MoveDeleter& operator=(MoveDeleter&& rhs) noexcept = delete; -}; - -template -class TestCallback : public MoveDeleter { -public: - TestCallback() = default; - - ValueType operator()(const ValueType& lhs, const ValueType& rhs) const { - return (lhs + rhs); - } -}; - -template -class TestCallback : public MoveDeleter { -public: - TestCallback() = default; - - void operator()() const {} -}; - -template -concept CanZipTransform = - requires(Function&& function, RangeTypes&&... ranges) { - views::zip_transform(std::forward(function), std::forward(ranges)...); - }; - template using AllView = views::all_t; template using TransformResultType = invoke_result_t&, - ranges::range_reference_t>>...>; + ranges::range_reference_t>...>; template constexpr auto& maybe_as_const(T& value) { @@ -67,331 +30,799 @@ constexpr auto& maybe_as_const(T& value) { } } +template +concept CanZipTransform = (ranges::viewable_range && ...) + && requires(Function&& function, RangeTypes&&... ranges) { + views::zip_transform( + std::forward(function), std::forward(ranges)...); + }; + +template +concept HasIteratorCategory = requires() { typename ranges::iterator_t::iterator_category; }; + +#pragma warning(push) +#pragma warning(disable : 4100) // unreferenced formal parameter + +#pragma warning(push) +#pragma warning(disable : 4365) // conversion from 'std::array::size_type' to 'int', signed/unsigned mismatch template constexpr bool test_one( TransformType_&& transform, const TransformedElementsContainer& transformed_elements, RangeTypes&&... ranges) { - using TransformType = remove_cvref_t; - using ZipTransformType = ranges::zip_transform_view...>; - - constexpr bool are_views = (ranges::view> && ...) && (sizeof...(RangeTypes) > 0); - constexpr bool is_transform = - is_object_v - && regular_invocable>...> - && _Can_reference>...>>; - - STATIC_ASSERT(ranges::view); +#pragma warning(pop) + // Ignore instances where one of the generated test ranges does not model + // ranges::viewable_range. + if constexpr ((ranges::viewable_range && ...)) { + using TransformType = remove_cvref_t; + using ZipTransformType = ranges::zip_transform_view...>; + using InnerView = ranges::zip_view...>; - // Validate iterator concept - { - constexpr auto check_iterator_concept_closure = []() { - using transform_result_t = TransformResultType; - - if constexpr (!is_reference_v) { - STATIC_ASSERT(ranges::input_range); - STATIC_ASSERT(!ranges::forward_range); - STATIC_ASSERT(!ranges::bidirectional_range); - STATIC_ASSERT(!ranges::random_access_range); - } else { - constexpr auto check_iterator_tags_closure = []() { - return ( - derived_from>>>::iterator_category, - TagType> - && ...); - }; + constexpr bool are_views = (ranges::view> && ...) && (sizeof...(RangeTypes) > 0); - STATIC_ASSERT(ranges::input_range); - STATIC_ASSERT(ranges::forward_range - == check_iterator_tags_closure.template operator()()); - STATIC_ASSERT(ranges::bidirectional_range - == check_iterator_tags_closure.template operator()()); - STATIC_ASSERT(ranges::random_access_range - == check_iterator_tags_closure.template operator()()); - } - }; + STATIC_ASSERT(ranges::view); - check_iterator_concept_closure.template operator()(); - check_iterator_concept_closure.template operator()(); - } + // Validate commonality + STATIC_ASSERT(ranges::common_range == ranges::common_range); - using InnerView = ranges::zip_view...>; + constexpr bool has_const_begin_end = + ranges::range + && regular_invocable...>; - // Validate commonality - STATIC_ASSERT(ranges::common_range == ranges::common_range); + if constexpr (has_const_begin_end) { + STATIC_ASSERT(ranges::common_range == ranges::common_range); + } - if constexpr (should_have_const_begin_end) { - STATIC_ASSERT(ranges::common_range == ranges::common_range); - } + // Validate conditional default-initializability + STATIC_ASSERT(is_default_constructible_v + == (is_default_constructible_v> + && is_default_constructible_v) ); + STATIC_ASSERT(is_nothrow_default_constructible_v + == (is_nothrow_default_constructible_v> + && is_nothrow_default_constructible_v) ); - // Validate conditional default-initializability - STATIC_ASSERT( - is_default_constructible_v == is_default_constructible_v> - && is_default_constructible_v); - STATIC_ASSERT(is_nothrow_default_constructible_v - == is_nothrow_default_constructible_v> - && is_nothrow_default_constructible_v); + // Validate range adaptor object + { + constexpr bool can_copy_construct_ranges = (!are_views || (copy_constructible> && ...)); + constexpr bool can_move_ranges = (are_views || (movable> && ...)); - // Validate range adaptor object - { - constexpr bool can_copy_construct_ranges = (!are_views || (copy_constructible> && ...)); - constexpr bool can_move_ranges = (are_views || (movable> && ...)); - - // ... with lvalue arguments - STATIC_ASSERT(CanZipTransform == can_copy_construct_ranges - && move_constructible); - if constexpr (CanZipTransform) { - using ExpectedZipTransformType = ZipTransformType; - constexpr bool is_noexcept = is_nothrow_move_constructible_v> - && is_nothrow_constructible_v; - - STATIC_ASSERT(same_as(transform), ranges...)), - ExpectedZipTransformType>); - STATIC_ASSERT( - noexcept(views::zip_transform(std::forward(transform), ranges...)) == is_noexcept); - } + // ... with lvalue arguments + STATIC_ASSERT(CanZipTransform + == (can_copy_construct_ranges && move_constructible) ); + if constexpr (CanZipTransform) { + using ExpectedZipTransformType = ZipTransformType; + constexpr bool is_noexcept = is_nothrow_move_constructible_v> + && is_nothrow_constructible_v&&...>; - // ... with const lvalue arguments - STATIC_ASSERT( - CanZipTransform&...> == can_copy_construct_ranges - && move_constructible); - if constexpr (CanZipTransform&...>) { - using ExpectedZipTransformType = - ranges::zip_transform_view&>...>; - constexpr bool is_noexcept = - is_nothrow_move_constructible_v> - && is_nothrow_constructible_v&>...>; + STATIC_ASSERT( + same_as(transform), ranges...)), + ExpectedZipTransformType>); + STATIC_ASSERT( + noexcept(views::zip_transform(std::forward(transform), ranges...)) == is_noexcept); + } - STATIC_ASSERT( - same_as(transform), as_const(ranges)...)), + // ... with const lvalue arguments + STATIC_ASSERT(CanZipTransform&...> + == (can_copy_construct_ranges && move_constructible) ); + if constexpr (CanZipTransform&...>) { + using ExpectedZipTransformType = + ranges::zip_transform_view&>...>; + constexpr bool is_noexcept = is_nothrow_move_constructible_v> + && is_nothrow_constructible_v&&...>; + + STATIC_ASSERT(same_as(transform), as_const(ranges)...)), ExpectedZipTransformType>); - STATIC_ASSERT(noexcept(views::zip_transform(std::forward(transform), as_const(ranges)...)) - == is_noexcept); - } - - // ... with rvalue arguments - STATIC_ASSERT(CanZipTransform> == can_move_ranges - && move_constructible); - if constexpr (CanZipTransform...>) { - using ExpectedZipTransformType = - ranges::zip_transform_view>...>; - constexpr bool is_noexcept = - is_nothrow_move_constructible_v> - && is_nothrow_constructible_v>...>; + STATIC_ASSERT( + noexcept(views::zip_transform(std::forward(transform), as_const(ranges)...)) + == is_noexcept); + } - STATIC_ASSERT( - same_as(transform), std::move(ranges)...)), + // ... with rvalue arguments + STATIC_ASSERT(CanZipTransform...> + == (can_move_ranges && move_constructible) ); + if constexpr (CanZipTransform...>) { + using ExpectedZipTransformType = + ranges::zip_transform_view>...>; + constexpr bool is_noexcept = is_nothrow_move_constructible_v> + && is_nothrow_constructible_v&&...>; + + STATIC_ASSERT(same_as(transform), std::move(ranges)...)), ExpectedZipTransformType>); - STATIC_ASSERT(noexcept(views::zip_transform(std::forward(transform), std::move(ranges)...)) - == is_noexcept); - } + STATIC_ASSERT( + noexcept(views::zip_transform(std::forward(transform), std::move(ranges)...)) + == is_noexcept); + } - // ... with const rvalue arguments - STATIC_ASSERT(CanZipTransform> == can_copy_construct_ranges - && move_constructible); - if constexpr (CanZipTransform>) { - using ExpectedZipTransformType = - ranges::zip_transform_view>...>; - constexpr bool is_noexcept = - is_nothrow_move_constructible_v> - && is_nothrow_constructible_v>...>; - - STATIC_ASSERT(same_as(transform), std::move(as_const(ranges))...)), - ExpectedZipTransformType>); - STATIC_ASSERT( - noexcept(views::zip_transform(std::forward(transform), std::move(as_const(ranges))...)) - == is_noexcept); + // ... with const rvalue arguments + STATIC_ASSERT(CanZipTransform...> + == (are_views && (copy_constructible> && ...) + && move_constructible) ); + if constexpr (CanZipTransform...>) { + using ExpectedZipTransformType = + ranges::zip_transform_view>...>; + constexpr bool is_noexcept = is_nothrow_move_constructible_v> + && is_nothrow_constructible_v&&...>; + + STATIC_ASSERT(same_as(transform), std::move(as_const(ranges))...)), + ExpectedZipTransformType>); + STATIC_ASSERT(noexcept(views::zip_transform( + std::forward(transform), std::move(as_const(ranges))...)) + == is_noexcept); + } } - } - if constexpr (move_constructible) { - // Validate deduction guide - same_as auto zipped_transformed_range = - ranges::zip_transform_view{std::forward(transform), std::forward(ranges)...}; + if constexpr (move_constructible) { + // Validate deduction guide + same_as auto zipped_transformed_range = ranges::zip_transform_view{ + std::forward(transform), std::forward(ranges)...}; - // Validate zip_transform_view::size() - STATIC_ASSERT(CanMemberSize == ranges::sized_range); - if constexpr (CanMemberSize) { - using expected_size_type = decltype(declval().size()); - same_as auto zip_transform_size = zipped_transformed_range.size(); + // Validate zip_transform_view::size() + STATIC_ASSERT(CanMemberSize == ranges::sized_range); + if constexpr (CanMemberSize) { + using expected_size_type = decltype(declval().size()); + same_as auto zip_transform_size = zipped_transformed_range.size(); - assert(zip_transform_size == ranges::size(transformed_elements)); - STATIC_ASSERT(noexcept(zipped_transformed_range.size()) == noexcept(declval().size())); - } + assert(zip_transform_size == ranges::size(transformed_elements)); + STATIC_ASSERT(noexcept(zipped_transformed_range.size()) == noexcept(declval().size())); + } - STATIC_ASSERT(CanMemberSize == ranges::sized_range); - if constexpr (CanMemberSize) { - using expected_size_type = decltype(declval().size()); - same_as auto zip_transform_size = as_const(zipped_transformed_range).size(); + STATIC_ASSERT(CanMemberSize == ranges::sized_range); + if constexpr (CanMemberSize) { + using expected_size_type = decltype(declval().size()); + same_as auto zip_transform_size = as_const(zipped_transformed_range).size(); - assert(zip_transform_size == ranges::size(transformed_elements)); - STATIC_ASSERT(noexcept(zipped_transformed_range.size()) == noexcept(declval().size())); - } + assert(zip_transform_size == ranges::size(transformed_elements)); + STATIC_ASSERT( + noexcept(zipped_transformed_range.size()) == noexcept(declval().size())); + } - const bool is_empty = ranges::empty(transformed_elements); + const bool is_empty = ranges::empty(transformed_elements); - // We don't want an empty results range, since we still need to do additional testing. - assert(!is_empty); + // We don't want an empty results range, since we still need to do additional testing. + assert(!is_empty); - // Validate view_interface::empty() and view_interface::operator bool - // - // From here on out, we'll be re-using concepts which we already verified to reduce - // redundancy. - STATIC_ASSERT(CanMemberEmpty == ranges::sized_range - || ranges::forward_range); - if constexpr (CanMemberEmpty) { - assert(zipped_transformed_range.empty() == is_empty); - } + // Validate view_interface::empty() and view_interface::operator bool + // + // From here on out, we'll be re-using concepts which we already verified to reduce + // redundancy. + STATIC_ASSERT(CanMemberEmpty + == (ranges::sized_range || ranges::forward_range) ); + if constexpr (CanMemberEmpty) { + assert(zipped_transformed_range.empty() == is_empty); + } - STATIC_ASSERT(CanMemberEmpty == ranges::sized_range - || ranges::forward_range); - if constexpr (CanMemberEmpty) { - assert(as_const(zipped_transformed_range).empty() == is_empty); - } + STATIC_ASSERT( + CanMemberEmpty + == (ranges::sized_range || ranges::forward_range) ); + if constexpr (CanMemberEmpty) { + assert(as_const(zipped_transformed_range).empty() == is_empty); + } - STATIC_ASSERT(CanBool == CanMemberEmpty); - if constexpr (CanBool) { - assert(static_cast(zipped_transformed_range) != is_empty); - } + STATIC_ASSERT(CanBool == CanMemberEmpty); + if constexpr (CanBool) { + assert(static_cast(zipped_transformed_range) != is_empty); + } - STATIC_ASSERT(CanBool == CanMemberEmpty); - if constexpr (CanBool) { - assert(static_cast(as_const(zipped_transformed_range)) != is_empty); - } + STATIC_ASSERT(CanBool == CanMemberEmpty); + if constexpr (CanBool) { + assert(static_cast(as_const(zipped_transformed_range)) != is_empty); + } - // Validate contents of zip-transformed range - assert(ranges::equal(zipped_transformed_range, transformed_elements)); + // Validate contents of zip-transformed range + assert(ranges::equal(zipped_transformed_range, transformed_elements)); #pragma warning(push) #pragma warning(disable : 4127) // Conditional Expression is Constant - if (!(ranges::forward_range> && ...)) // intentionally not if constexpr - { - return false; - } + if (!(ranges::forward_range> && ...)) // intentionally not if constexpr + { + return true; + } #pragma warning(pop) - // Validate view_interface::data() - // - // This should never exist because zip_transform_view does not model - // std::contiguous_range. - STATIC_ASSERT(!ranges::contiguous_range); - STATIC_ASSERT(!CanMemberData); - STATIC_ASSERT(!ranges::contiguous_range); - STATIC_ASSERT(!CanMemberData); + // Validate view_interface::data() + // + // This should never exist because zip_transform_view does not model + // std::contiguous_range. + STATIC_ASSERT(!ranges::contiguous_range); + STATIC_ASSERT(!CanMemberData); + STATIC_ASSERT(!ranges::contiguous_range); + STATIC_ASSERT(!CanMemberData); + + // Validate view_interface::front() + { + const auto validate_front_closure = [&]() { + STATIC_ASSERT(CanMemberFront> + == ranges::forward_range>); + if constexpr (CanMemberFront>) { + using transform_result_t = TransformResultType; + same_as auto first_result = + maybe_as_const(zipped_transformed_range).front(); + + assert(first_result == *ranges::begin(transformed_elements)); + } + }; - // Validate view_interface::front() - { - constexpr auto validate_front_closure = [&]() { - STATIC_ASSERT(CanMemberFront> - == ranges::forward_range>); - if constexpr (CanMemberFront>) { - using transform_result_t = TransformResultType; - same_as auto first_result = - maybe_as_const(zipped_transformed_range).front(); + validate_front_closure.template operator()(); + validate_front_closure.template operator()(); + } + + // Validate view_interface::back() + { + const auto validate_back_closure = [&]() { + STATIC_ASSERT(CanMemberBack> + == (ranges::bidirectional_range> + && ranges::common_range>) ); + if constexpr (CanMemberBack>) { + using transform_result_t = TransformResultType; + same_as auto last_result = + maybe_as_const(zipped_transformed_range).back(); + + assert(last_result == *ranges::prev(ranges::end(transformed_elements))); + } + }; + + validate_back_closure.template operator()(); + validate_back_closure.template operator()(); + } + + // Validate view_interface::operator[] + { + const auto validate_random_access_closure = [&]() { + STATIC_ASSERT(CanIndex> + == ranges::random_access_range>); + if constexpr (CanIndex>) { + using transform_result_t = TransformResultType; + same_as auto first_result = + maybe_as_const(zipped_transformed_range)[0]; + + assert(first_result == ranges::begin(transformed_elements)[0]); + } + }; + + validate_random_access_closure.template operator()(); + validate_random_access_closure.template operator()(); + } + + // Validate zip_transform_view::begin() and zip_transform_view::end() + STATIC_ASSERT(CanMemberBegin); + STATIC_ASSERT(CanMemberEnd); + { + const same_as> auto begin_itr = zipped_transformed_range.begin(); + [[maybe_unused]] const same_as> auto end_sentinel = + zipped_transformed_range.end(); + + assert(*begin_itr == *ranges::begin(transformed_elements)); + assert(static_cast( + ranges::distance(zipped_transformed_range.begin(), zipped_transformed_range.end())) + == ranges::size(transformed_elements)); + + STATIC_ASSERT(noexcept(zipped_transformed_range.begin()) == noexcept(declval().begin()) + && is_nothrow_move_constructible_v>); + STATIC_ASSERT(noexcept(zipped_transformed_range.end()) == noexcept(declval().end()) + && is_nothrow_move_constructible_v>); + } + + STATIC_ASSERT(CanMemberBegin == has_const_begin_end); + STATIC_ASSERT(CanMemberEnd == has_const_begin_end); + if constexpr (has_const_begin_end) { + const same_as> auto begin_itr = + as_const(zipped_transformed_range).begin(); + [[maybe_unused]] const same_as> auto end_sentinel = + as_const(zipped_transformed_range).end(); + + assert(*begin_itr == *ranges::begin(transformed_elements)); + assert(static_cast( + ranges::distance(zipped_transformed_range.begin(), zipped_transformed_range.end())) + == ranges::size(transformed_elements)); + STATIC_ASSERT(noexcept(as_const(zipped_transformed_range).begin()) + == noexcept(declval().begin()) + && is_nothrow_move_constructible_v>); + STATIC_ASSERT( + noexcept(as_const(zipped_transformed_range).end()) == noexcept(declval().end()) + && is_nothrow_move_constructible_v>); + } + + const auto validate_iterators_closure = [&transformed_elements]( + LocalZipTransformType& relevant_range) { + constexpr bool is_const = same_as>; + using BaseType = ranges::_Maybe_const; + using ZipIteratorTupleType = + tuple>>...>; + + // Validate iterator type aliases + { + // Validate iterator_category + if constexpr (ranges::forward_range) { + STATIC_ASSERT(HasIteratorCategory); + + using transform_result_t = TransformResultType; + + if constexpr (!is_reference_v) { + STATIC_ASSERT(same_as::iterator_category, + input_iterator_tag>); + } else { + constexpr auto check_iterator_tags_closure = []() { + return ( + derived_from>>::iterator_category, + TagType> + && ...); + }; + + if constexpr (check_iterator_tags_closure + .template operator()()) { + STATIC_ASSERT( + same_as::iterator_category, + random_access_iterator_tag>); + } else if constexpr (check_iterator_tags_closure + .template operator()()) { + STATIC_ASSERT( + same_as::iterator_category, + bidirectional_iterator_tag>); + } else if constexpr (check_iterator_tags_closure + .template operator()()) { + STATIC_ASSERT( + same_as::iterator_category, + forward_iterator_tag>); + } else { + STATIC_ASSERT( + same_as::iterator_category, + input_iterator_tag>); + } + } + } else { + STATIC_ASSERT(!HasIteratorCategory); + } + + // Validate iterator_concept + STATIC_ASSERT(same_as::iterator_concept, + typename ranges::iterator_t::iterator_concept>); + + // Validate value_type + STATIC_ASSERT(same_as::value_type, + remove_cvref_t>>); + + // Validate difference_type + STATIC_ASSERT(same_as::difference_type, + ranges::range_difference_t>); + } + + // Validate iterator constructors + STATIC_ASSERT(is_default_constructible_v> + == is_default_constructible_v>); + STATIC_ASSERT(is_nothrow_default_constructible_v> + == is_nothrow_default_constructible_v>); + + if constexpr (is_const + && convertible_to, ranges::iterator_t>) { + STATIC_ASSERT(noexcept(ranges::iterator_t{ + declval>>()}) + == is_nothrow_convertible_v, + ranges::iterator_t>); + } + same_as> auto itr = relevant_range.begin(); + + // Validate iterator operator overloads + { + const auto first_result = *itr; assert(first_result == *ranges::begin(transformed_elements)); + + // NOTE: The actual noexcept specification for zip_transform_view::iterator::operator*() is as + // follows: + // + // Let Is be the pack 0, 1, ..., (sizeof...(Views)-1). The exception specification is equivalent to: + // noexcept(invoke(*parent_->fun_, *std::get(inner_.current_)...)). + // + // Notably, parent_t is a pointer and inner_.current_ is a tuple, and operator->() on a pointer and + // std::get(std::tuple<...>) are both noexcept. We thus simplify the noexcept check as follows: + STATIC_ASSERT( + noexcept(*itr) + == noexcept(invoke(*declval&>(), + *declval< + const ranges::iterator_t>>&>()...))); } - }; - validate_front_closure.template operator()(); - validate_front_closure.template operator()(); - } + STATIC_ASSERT(noexcept(++itr) == noexcept(++declval&>())); + + if constexpr (ranges::forward_range) { + same_as> auto duplicate_itr = itr++; + assert(*duplicate_itr == *ranges::begin(transformed_elements)); + STATIC_ASSERT( + noexcept(itr++) + == is_nothrow_copy_constructible_v>&& noexcept( + ++itr)); + } else { + itr++; + STATIC_ASSERT(noexcept(itr++) == noexcept(++itr)); + } - // Validate view_interface::back() - { - constexpr auto validate_back_closure = [&]() { - STATIC_ASSERT(CanMemberBack> - == ranges::bidirectional_range> - && ranges::common_range>); - if constexpr (CanMemberBack>) { - using transform_result_t = TransformResultType; - same_as auto last_result = - maybe_as_const(zipped_transformed_range).back(); - - assert(last_result == *ranges::prev(ranges::end(transformed_elements))); + assert(*++itr == transformed_elements[2]); + + if constexpr (ranges::bidirectional_range) { + assert(*itr-- == transformed_elements[2]); + STATIC_ASSERT( + noexcept(itr--) + == is_nothrow_copy_constructible_v>&& noexcept( + --itr)); + + assert(*--itr == transformed_elements[0]); + STATIC_ASSERT(noexcept(--itr) == noexcept(--declval&>())); } - }; - validate_back_closure.template operator()(); - validate_back_closure.template operator()(); - } + if constexpr (ranges::random_access_range) { + itr += 2; + assert(*itr == transformed_elements[2]); + STATIC_ASSERT(noexcept(itr += 2) == noexcept(declval&>() += 2)); + + itr -= 2; + assert(*itr == transformed_elements[0]); + STATIC_ASSERT(noexcept(itr -= 2) == noexcept(declval&>() -= 2)); + + assert(itr[2] == transformed_elements[2]); + STATIC_ASSERT( + noexcept(itr[2]) + == noexcept(apply([](const IteratorTypes&... itrs) noexcept( + noexcept(invoke(*declval&>(), + itrs[static_cast>(2)]...))) + -> decltype(auto) { return true; }, + declval()))); + + const same_as> auto itr2 = (itr + 2); + assert(*itr2 == transformed_elements[2]); + STATIC_ASSERT(noexcept(itr + 2) == noexcept(declval&>() + 2) + && is_nothrow_move_constructible_v>); + + const same_as> auto itr3 = (2 + itr); + assert(*itr3 == transformed_elements[2]); + STATIC_ASSERT(noexcept(2 + itr) == noexcept(declval&>() + 2) + && is_nothrow_move_constructible_v>); + + const same_as> auto itr4 = (itr3 - 2); + assert(*itr4 == transformed_elements[0]); + STATIC_ASSERT(noexcept(itr3 - 2) == noexcept(declval&>() - 2) + && is_nothrow_move_constructible_v>); + + using three_way_ordering_category = decltype(itr <=> itr2); + + assert(itr <=> itr4 == three_way_ordering_category::equivalent); + assert(itr <=> itr2 == three_way_ordering_category::less); + assert(itr2 <=> itr == three_way_ordering_category::greater); + + STATIC_ASSERT(noexcept(itr <=> itr2) + == noexcept(declval&>() + <=> declval&>())); + } - // Validate view_interface::operator[] - { - constexpr auto validate_random_access_closure = [&]() { - STATIC_ASSERT(CanIndex> - == ranges::random_access_range>); - if constexpr (CanIndex>) { - using transform_result_t = TransformResultType; - same_as auto first_result = - maybe_as_const(zipped_transformed_range)[0]; - - assert(first_result == ranges::begin(transformed_elements)[0]); + if constexpr (equality_comparable>) { + same_as> auto advanced_itr1 = relevant_range.begin(); + ranges::advance(advanced_itr1, 2); + + same_as> auto advanced_itr2 = relevant_range.begin(); + ranges::advance(advanced_itr2, 2); + + assert(advanced_itr1 == advanced_itr2); + STATIC_ASSERT(noexcept(advanced_itr1 == advanced_itr2) + == noexcept(declval&>() + == declval&>())); + + assert(relevant_range.begin() != advanced_itr1); + } + + if constexpr (!ranges::common_range) { + // Validate sentinel constructors + STATIC_ASSERT(is_default_constructible_v> + == is_default_constructible_v>); + STATIC_ASSERT(is_nothrow_default_constructible_v> + == is_nothrow_default_constructible_v>); + + if constexpr (is_const + && convertible_to, + ranges::sentinel_t>) { + STATIC_ASSERT(noexcept(ranges::sentinel_t{ + declval>>()}) + == is_nothrow_move_constructible_v>); + } + + const same_as> auto sentinel = relevant_range.end(); + + // Validate sentinel operator overloads + { + const auto validate_iterator_sentinel_equality_closure = [&]() { + using comparison_iterator_t = + ranges::iterator_t>; + using comparison_sentinel_t = ranges::sentinel_t; + + if constexpr (sentinel_for) { + auto end_iterator = relevant_range.begin(); + ranges::advance(end_iterator, ranges::size(transformed_elements)); + + assert(end_iterator == sentinel); + STATIC_ASSERT(noexcept(end_iterator == sentinel) + == noexcept(declval() + == declval())); + + assert(itr != sentinel); + } + }; + + validate_iterator_sentinel_equality_closure.template operator()(); + validate_iterator_sentinel_equality_closure.template operator()(); + } + + { + const auto validate_iterator_sentinel_difference_closure = [&]() { + using comparison_iterator_t = + ranges::iterator_t>; + using comparison_sentinel_t = ranges::sentinel_t; + + if constexpr (sized_sentinel_for) { + using difference_type = + ranges::range_difference_t>; + + const auto comparison_itr = maybe_as_const(relevant_range).begin(); + + const same_as auto diff1 = (sentinel - comparison_itr); + assert(diff1 == static_cast(ranges::size(transformed_elements))); + STATIC_ASSERT(noexcept(sentinel - comparison_itr) + == noexcept(declval() + - declval())); + + const same_as auto diff2 = (comparison_itr - sentinel); + assert(diff2 == -static_cast(ranges::size(transformed_elements))); + STATIC_ASSERT(noexcept(comparison_itr - sentinel) + == noexcept(declval() + - declval())); + } + }; + + validate_iterator_sentinel_difference_closure.template operator()(); + validate_iterator_sentinel_difference_closure.template operator()(); + } } }; - validate_random_access_closure.template operator()(); - validate_random_access_closure.template operator()(); + validate_iterators_closure(zipped_transformed_range); + validate_iterators_closure(as_const(zipped_transformed_range)); } + } - // Validate zip_transform_view::begin() and zip_transform_view::end() - STATIC_ASSERT(CanMemberBegin); - STATIC_ASSERT(CanMemberEnd) { - same_as> auto begin_itr = zipped_transformed_range.begin(); - same_as> auto end_sentinel = zipped_transformed_range.end(); - - assert(*begin_itr == *ranges::begin(transformed_elements)); - assert(ranges::distance(begin_itr, end_sentinel) == ranges::size(transformed_elements)); - STATIC_ASSERT(noexcept(zipped_transformed_range.begin()) - == is_nothrow_constructible_v, ZipTransformType&, - ranges::iterator_t>); - - if constexpr (ranges::common_range) { - STATIC_ASSERT(noexcept(zipped_transformed_range.end()) - == is_nothrow_constructible_v, ZipTransformType&, - ranges::iterator_t>); - } else { - STATIC_ASSERT( - noexcept(zipped_transformed_range.end()) - == is_nothrow_constructible_v, ranges::sentinel_t>); - } - } + return true; +} - constexpr bool has_const_begin_end = - ranges::range - && regular_invocable...>; +#pragma warning(pop) - STATIC_ASSERT(CanMemberBegin == has_const_begin_end); - STATIC_ASSERT(CanMemberEnd == has_const_begin_end); - if constexpr (has_const_begin_end) { - same_as> auto begin_itr = - as_const(zipped_transformed_range).begin(); - same_as> auto end_sentinel = - as_const(zipped_transformed_range).end(); - - assert(*begin_itr == *ranges::begin(transformed_elements)); - assert(ranges::distance(begin_itr, end_sentinel) == ranges::size(transformed_elements)); - STATIC_ASSERT(noexcept(as_const(zipped_transformed_range).begin()) - == is_nothrow_constructible_v, - const ZipTransformType&, ranges::iterator_t>); - - if constexpr (ranges::common_range) { - STATIC_ASSERT(noexcept(as_const(zipped_transformed_range).end()) - == is_nothrow_constructible_v, - const ZipTransformType&, ranges::iterator_t>); - } else { - STATIC_ASSERT(noexcept(as_const(zipped_transformed_range).end()) - == is_nothrow_constructible_v, - ranges::sentinel_t>); - } - } +constexpr auto one_element_transform_closure = [](const ElementType& a) { return (a * 5); }; + +constexpr auto three_element_transform_closure = [](const Type1& a, + const Type2& b, const Type3& c) { return (a * b + c); }; + +constexpr array test_element_array_one{0, 1, 2, 3, 4, 5, 6, 7}; +constexpr array test_element_array_two{5, 13, 6, -4, 12}; +constexpr array test_element_array_three{6534, 23, 62, -124, 6, 42, 9}; + +constexpr auto single_range_transform_results_array = []() { + auto transformed_elements_array{test_element_array_one}; + ranges::transform(transformed_elements_array, transformed_elements_array.begin(), one_element_transform_closure); + + return transformed_elements_array; +}(); + +constexpr auto three_range_transform_results_array = []() { + constexpr std::size_t results_array_size = + (min) ((min) (test_element_array_one.size(), test_element_array_two.size()), test_element_array_three.size()); + array transformed_elements_array{}; + + for (auto [destValue, a, b, c] : views::zip( + transformed_elements_array, test_element_array_one, test_element_array_two, test_element_array_three)) { + destValue = three_element_transform_closure(a, b, c); + } + + return transformed_elements_array; +}(); + +// zip_transform_view is implemented in terms of zip_view, so it inherits +// its sensitivity towards all of the traits which zip_view is sensitive to. This includes +// the following: +// +// - The categories of RangeTypes... +// - The commonality of RangeTypes... +// - Whether or not the size() member exists for each of RangeTypes..., as it is used by ranges::size() +// - Whether or not each range's iterator and sentinel models sized_sentinel_for +// - The sizeof...(RangeTypes) +// +// Other conditions are irrelevant. + +template +class range_type_solver { +protected: + template + using range_type = test::range}), + test::ProxyRef{derived_from}, test::CanView::yes, + test::Copyability::move_only>; +}; + +template <> +class range_type_solver { +protected: + template + using range_type = test::range})>; +}; + +template +class instantiator_impl : private range_type_solver { +private: + template + using range_type = typename range_type_solver::template range_type; + + using standard_range_type = range_type; + + using differing_category_range_type = + range_type, forward_iterator_tag, input_iterator_tag>, + const int, IsSized, IsCommon, Diff>; + using differing_size_member_range_type = range_type; + using differing_is_common_range_type = range_type; + using differing_iterator_sentinel_diff_range_type = range_type; + + static constexpr void test_single_range() { + auto single_range = standard_range_type{span{test_element_array_one}}; + test_one(one_element_transform_closure, single_range_transform_results_array, single_range); + } + + template + static constexpr void test_three_ranges() { + auto first_range = DifferingRangeType{span{test_element_array_one}}; + auto second_range = standard_range_type{span{test_element_array_two}}; + auto third_range = standard_range_type{span{test_element_array_three}}; - // TODO: Add testing for iterators and sentinels returned by zip_transform_view! + test_one(three_element_transform_closure, three_range_transform_results_array, first_range, second_range, + third_range); } +public: + static constexpr void call() { + // Test the single-range use of views::zip_transform (i.e., sizeof...(RangeTypes) == 1). + test_single_range(); + + // Test three ranges with views::zip_transform with... + + // all of their traits being the same,... + test_three_ranges(); + + // one range having a different category,... + test_three_ranges(); + + // one range having a different path for ranges::size(),... + test_three_ranges(); + + // one range having a different commonality,... + test_three_ranges(); + + // and one range having iterators and sentinels which model sized_sentinel_for + // differently. + test_three_ranges(); + } +}; + +template +class instantiator : public instantiator_impl {}; + +template +class move_only_view_instantiator : public instantiator_impl {}; + +template class InstantiatorType> +constexpr bool instantiation_test_for_category() { + using test::Sized, test::Common, test::CanDifference; + + InstantiatorType::call(); + InstantiatorType::call(); + InstantiatorType::call(); + InstantiatorType::call(); + InstantiatorType::call(); + InstantiatorType::call(); + InstantiatorType::call(); + InstantiatorType::call(); + + return true; +} + +template