diff --git a/stl/inc/ranges b/stl/inc/ranges index 19f422d78f..b3c97f34e3 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -7306,6 +7306,12 @@ namespace ranges { private: friend zip_view; + template + requires ((view<_OtherViews> && ...) && (sizeof...(_OtherViews) > 0) + && is_object_v<_Func> && regular_invocable<_Func&, range_reference_t<_OtherViews>...> + && _Can_reference...>>) + friend class zip_transform_view; + using _My_tuple = tuple>...>; /* [[no_unique_address]] */ _My_tuple _Current; @@ -7580,7 +7586,7 @@ namespace ranges { }; template - static constexpr bool _Is_end_noexcept = []() { + _NODISCARD static _CONSTEVAL bool _Is_end_noexcept() noexcept { if constexpr (!_Zip_is_common<_Maybe_const<_IsConst, _ViewTypes>...>) { return noexcept(_Sentinel<_IsConst>{ _Tuple_transform(_RANGES end, _STD declval<_Maybe_const<_IsConst, tuple<_ViewTypes...>>&>())}); @@ -7592,7 +7598,7 @@ namespace ranges { return noexcept(_Iterator<_IsConst>{ _Tuple_transform(_RANGES end, _STD declval<_Maybe_const<_IsConst, tuple<_ViewTypes...>>&>())}); } - }(); + } public: zip_view() noexcept((is_nothrow_default_constructible_v<_ViewTypes> && ...)) = default; @@ -7615,7 +7621,7 @@ namespace ranges { return _Iterator{_Tuple_transform(_RANGES begin, _Views)}; } - _NODISCARD constexpr auto end() noexcept(_Is_end_noexcept) // strengthened + _NODISCARD constexpr auto end() noexcept(_Is_end_noexcept()) // strengthened requires (!(_Simple_view<_ViewTypes> && ...)) { if constexpr (!_Zip_is_common<_ViewTypes...>) { @@ -7627,7 +7633,7 @@ namespace ranges { } } - _NODISCARD constexpr auto end() const noexcept(_Is_end_noexcept) // strengthened + _NODISCARD constexpr auto end() const noexcept(_Is_end_noexcept()) // strengthened requires (range && ...) { if constexpr (!_Zip_is_common) { @@ -7698,6 +7704,364 @@ 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 + struct _Category_base {}; + + template + requires forward_range<_Maybe_const<_IsConst, _Inner_view>> + struct _Category_base<_IsConst> { + private: + template + static constexpr bool _Iterators_derive_from_tag = + (derived_from< + typename iterator_traits>>::iterator_category, + _Tag_type> + && ...); + + using _Invoke_t = invoke_result_t<_Maybe_const<_IsConst, _Func>&, + range_reference_t<_Maybe_const<_IsConst, _ViewTypes>>...>; + + public: + using iterator_category = conditional_t, input_iterator_tag, + conditional_t<_Iterators_derive_from_tag, random_access_iterator_tag, + conditional_t<_Iterators_derive_from_tag, bidirectional_iterator_tag, + conditional_t<_Iterators_derive_from_tag, 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(), _Inner._Current))) { + return _STD apply(_Dereference_closure(), _Inner._Current); + } + + 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(_Subscript_closure(_Where), _Inner._Current))) // strengthened + requires random_access_range<_Base_t> + { + return _STD apply(_Subscript_closure(_Where), _Inner._Current); + } + + _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 _Subscript_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; + + // TRANSITION, DevCom-10253131 + template + using _Iter_sent_difference_type = range_difference_t<_Maybe_const<_IteratorConst, _Inner_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_inner( + 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>> + _NODISCARD_FRIEND constexpr bool operator==(const _Iterator<_IteratorConst>& _Lhs, + const _Sentinel& _Rhs) noexcept(noexcept(_Get_iterator_inner(_Lhs) == _Rhs._Inner)) /* strengthened */ { + return _Get_iterator_inner(_Lhs) == _Rhs._Inner; + } + + template + requires sized_sentinel_for<_Zentinel<_IsConst>, _Ziperator<_IteratorConst>> + _NODISCARD_FRIEND constexpr _Iter_sent_difference_type<_IteratorConst> // TRANSITION, DevCom-10253131 + operator-(const _Iterator<_IteratorConst>& _Lhs, const _Sentinel& _Rhs) noexcept( + noexcept(_Get_iterator_inner(_Lhs) - _Rhs._Inner)) /* strengthened */ { + return _Get_iterator_inner(_Lhs) - _Rhs._Inner; + } + + template + requires sized_sentinel_for<_Zentinel<_IsConst>, _Ziperator<_IteratorConst>> + _NODISCARD_FRIEND constexpr _Iter_sent_difference_type<_IteratorConst> // TRANSITION, DevCom-10253131 + operator-(const _Sentinel& _Lhs, const _Iterator<_IteratorConst>& _Rhs) noexcept( + noexcept(_Lhs._Inner - _Get_iterator_inner(_Rhs))) /* strengthened */ { + return _Lhs._Inner - _Get_iterator_inner(_Rhs); + } + }; + + template + _NODISCARD static _CONSTEVAL 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<_Func>&& + is_nothrow_constructible_v<_Inner_view, remove_reference_t<_ViewTypes>&&...>) // strengthened + : _Function(in_place, _STD move(_Function_)), _Zip(_STD move(_Views)...) {} + + _NODISCARD constexpr auto begin() noexcept(noexcept(_Iterator{*this, _Zip.begin()})) /* strengthened */ { + return _Iterator{*this, _Zip.begin()}; + } + + _NODISCARD constexpr auto begin() const noexcept(noexcept(_Iterator{*this, _Zip.begin()})) // strengthened + requires _Enable_const_begin_end + { + return _Iterator{*this, _Zip.begin()}; + } + + _NODISCARD constexpr auto end() noexcept(_Is_end_noexcept()) /* strengthened */ { + if constexpr (common_range<_Inner_view>) { + return _Iterator{*this, _Zip.end()}; + } else { + return _Sentinel{_Zip.end()}; + } + } + + _NODISCARD 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()}; + } + } + + _NODISCARD constexpr auto size() noexcept(noexcept(_Zip.size())) // strengthened + requires sized_range<_Inner_view> + { + return _Zip.size(); + } + + _NODISCARD 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) { + return true; + } else { + return noexcept(zip_transform_view(_STD declval<_Func>(), _STD declval<_Types>()...)); + } + }(); + + template + static constexpr bool _Enable_cpo = []() noexcept { + if constexpr (sizeof...(_Types) == 0) { + // This is a more efficient implementation of the CPO requirements listed in + // N4928 [range.zip.transform.overview]/2.1.1. + using _Decayed_func = decay_t<_Func>; + if constexpr (move_constructible<_Decayed_func> && regular_invocable<_Decayed_func&>) { + return !is_void_v>; + } else { + return false; + } + } else { + // clang-format off + return requires(_Func&& _Function, _Types&&... _Args) + { zip_transform_view(_STD forward<_Func>(_Function), _STD forward<_Types>(_Args)...); }; + // clang-format on + } + }(); + + public: + template + requires _Enable_cpo<_Func, _Types...> + _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<_Func>; + return empty_view>>{}; + } 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 diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 88cf2c26ae..705e35a413 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 diff --git a/tests/std/test.lst b/tests/std/test.lst index 9652c56a55..05bb45b73c 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -563,6 +563,7 @@ tests\P2302R4_ranges_alg_contains tests\P2302R4_ranges_alg_contains_subrange tests\P2321R2_proxy_reference tests\P2321R2_views_zip +tests\P2321R2_views_zip_transform tests\P2322R6_ranges_alg_fold tests\P2387R3_bind_back tests\P2387R3_pipe_support_for_user_defined_range_adaptors diff --git a/tests/std/tests/P2321R2_views_zip/test.cpp b/tests/std/tests/P2321R2_views_zip/test.cpp index 0a61a61863..1bb09120cc 100644 --- a/tests/std/tests/P2321R2_views_zip/test.cpp +++ b/tests/std/tests/P2321R2_views_zip/test.cpp @@ -328,7 +328,7 @@ constexpr bool test_one(TestContainerType& test_container, RangeTypes&&... range // 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 + // From here on out, we'll be reusing concepts which we already verified to reduce // redundancy. STATIC_ASSERT(CanMemberEmpty == (ranges::sized_range || ranges::forward_range) ); if constexpr (CanMemberEmpty) { @@ -710,16 +710,16 @@ class instantiator_impl : private range_type_solver { // Test three ranges with views::zip with... - // all of their traits being the same,... + // all of their traits being the same, ... test_three_ranges>(three_element_container_instance); - // one range having a different category,... + // one range having a different category, ... test_three_ranges(three_element_container_instance); - // one range having a different path for ranges::size(),... + // one range having a different path for ranges::size(), ... test_three_ranges(three_element_container_instance); - // one range having a different commonality,... + // one range having a different commonality, ... test_three_ranges(three_element_container_instance); // and one range having iterators and sentinels which model sized_sentinel_for 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..dc4b2759c0 --- /dev/null +++ b/tests/std/tests/P2321R2_views_zip_transform/env.lst @@ -0,0 +1,13 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +# NOTE: zip_view::_Iterator (used internally by zip_transform_view) defines an ADL +# overload for iter_move which causes an ambiguous symbol error when compiling +# without /permissive- set. This occurs even though zip_transform_view::_Iterator +# does not define an ADL overload for iter_move. +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 new file mode 100644 index 0000000000..bf5508502f --- /dev/null +++ b/tests/std/tests/P2321R2_views_zip_transform/test.cpp @@ -0,0 +1,861 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +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 +concept CanZipTransform = (ranges::viewable_range && ...) + && requires(Function&& func, RangeTypes&&... test_ranges) { + views::zip_transform( + std::forward(func), std::forward(test_ranges)...); + }; + +template +concept HasIteratorCategory = requires { typename ranges::iterator_t::iterator_category; }; + +#pragma warning(push) +#pragma warning(disable : 4365) // conversion from 'std::array::size_type' to 'int', signed/unsigned mismatch +template +constexpr bool validate_iterators_sentinels( + LocalZipTransformType& relevant_range, const TransformedElementsContainer& transformed_elements) { +#pragma warning(pop) + constexpr bool is_const = same_as>; + + using InnerView = ranges::zip_view...>; + using BaseType = ranges::_Maybe_const; + using ZipIteratorTupleType = tuple>>...>; + + // Validate iterator type aliases + { + // Validate iterator_category + if constexpr (ranges::forward_range) { + STATIC_ASSERT(HasIteratorCategory); + + using Cat = typename ranges::iterator_t::iterator_category; + using transform_result_t = TransformResultType; + + if constexpr (!is_reference_v) { + STATIC_ASSERT(same_as); + } 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); + } else if constexpr (check_iterator_tags_closure.template operator()()) { + STATIC_ASSERT(same_as); + } else if constexpr (check_iterator_tags_closure.template operator()()) { + STATIC_ASSERT(same_as); + } else { + STATIC_ASSERT(same_as); + } + } + } 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>>&>()...))); + } + + 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)); + } + + 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&>())); + } + + 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]); + { + constexpr bool is_random_access_noexcept = + noexcept(apply([](const IteratorTypes&... itrs) noexcept( + noexcept(invoke(*declval&>(), + itrs[static_cast>(2)]...))) { return true; }, + declval())); + STATIC_ASSERT(noexcept(itr[2]) == is_random_access_noexcept); + } + + 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&>())); + } + + 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()(); + } + } + + return true; +} + +#pragma warning(push) +#pragma warning(disable : 4100) // unreferenced formal parameter + +template +constexpr bool test_one(TransformType_&& transformer, const TransformedElementsContainer& transformed_elements, + RangeTypes&&... test_ranges) { + // 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...>; + + constexpr bool are_views = (ranges::view> && ...) && (sizeof...(RangeTypes) > 0); + + STATIC_ASSERT(ranges::view); + + // Validate commonality + STATIC_ASSERT(ranges::common_range == ranges::common_range); + + constexpr bool has_const_begin_end = + ranges::range + && regular_invocable...>; + + if constexpr (has_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(transformer), test_ranges...)), + ExpectedZipTransformType>); + STATIC_ASSERT(noexcept(views::zip_transform(std::forward(transformer), test_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(transformer), as_const(test_ranges)...)), + ExpectedZipTransformType>); + STATIC_ASSERT( + noexcept(views::zip_transform(std::forward(transformer), as_const(test_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(transformer), std::move(test_ranges)...)), + ExpectedZipTransformType>); + STATIC_ASSERT( + noexcept(views::zip_transform(std::forward(transformer), std::move(test_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(transformer), + std::move(as_const(test_ranges))...)), + ExpectedZipTransformType>); + STATIC_ASSERT(noexcept(views::zip_transform( + std::forward(transformer), std::move(as_const(test_ranges))...)) + == is_noexcept); + } + } + + if constexpr (move_constructible) { + // Validate deduction guide + same_as auto zipped_transformed_range = ranges::zip_transform_view{ + std::forward(transformer), std::forward(test_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(as_const(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 reusing 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 view_interface::cbegin() and view_interface::cend() + STATIC_ASSERT(CanMemberCBegin); + STATIC_ASSERT(CanMemberCEnd); + { + STATIC_ASSERT( + is_same_v>); + STATIC_ASSERT( + is_same_v>); + + if constexpr ((derived_from, forward_iterator_tag> && ...)) { + const auto cbegin_itr = zipped_transformed_range.cbegin(); + [[maybe_unused]] const auto cend_sen = zipped_transformed_range.cend(); + + assert(*cbegin_itr == *ranges::begin(transformed_elements)); + assert(static_cast( + ranges::distance(zipped_transformed_range.cbegin(), zipped_transformed_range.cend())) + == ranges::size(transformed_elements)); + } + } + + STATIC_ASSERT(CanMemberCBegin == has_const_begin_end); + STATIC_ASSERT(CanMemberCEnd == has_const_begin_end); + if constexpr (has_const_begin_end) { + STATIC_ASSERT(is_same_v>); + STATIC_ASSERT(is_same_v>); + + if constexpr ((derived_from, forward_iterator_tag> && ...)) { + const same_as> auto cbegin_itr = + as_const(zipped_transformed_range).cbegin(); + [[maybe_unused]] const same_as> auto cend_sen = + as_const(zipped_transformed_range).cend(); + + assert(*cbegin_itr == *ranges::begin(transformed_elements)); + assert(static_cast(ranges::distance( + as_const(zipped_transformed_range).cbegin(), as_const(zipped_transformed_range).cend())) + == ranges::size(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 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::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_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( + as_const(zipped_transformed_range).begin(), as_const(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>); + } + + validate_iterators_sentinels(zipped_transformed_range, transformed_elements); + validate_iterators_sentinels( + as_const(zipped_transformed_range), transformed_elements); + } + } + + return true; +} + +#pragma warning(pop) + +constexpr auto one_element_transform_closure = [](const auto& a) { return a * 5; }; + +constexpr auto three_element_transform_closure = [](const auto& a, const auto& b, const auto& 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 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() { + standard_range_type single_range{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() { + DifferingRangeType first_range{span{test_element_array_one}}; + standard_range_type second_range{span{test_element_array_two}}; + standard_range_type third_range{span{test_element_array_three}}; + + 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; + +#ifndef _PREFAST_ // TRANSITION, GH-1030 + InstantiatorType::call(); + InstantiatorType::call(); + InstantiatorType::call(); + InstantiatorType::call(); + InstantiatorType::call(); + InstantiatorType::call(); + InstantiatorType::call(); +#endif // TRANSITION, GH-1030 + InstantiatorType::call(); + + return true; +} + +template