diff --git a/stl/inc/ranges b/stl/inc/ranges index 2617b7c4c4..2701519283 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -9254,7 +9254,7 @@ namespace ranges { is_nothrow_move_constructible_v<_Inner_iterator<_Const>>) // strengthened : _Parent(_STD addressof(_Parent_)), _Inner(_STD move(_Inner_)) {} - _NODISCARD static consteval auto _Get_iterator_category() noexcept { + _NODISCARD static _CONSTEVAL auto _Get_iterator_category() noexcept { if constexpr (!is_reference_v<_Invoke_result_with_repeated_type<_Maybe_const<_Const, _Fn>&, range_reference_t<_Base>, _Nx>>) { return input_iterator_tag{}; @@ -9274,7 +9274,7 @@ namespace ranges { } template - _NODISCARD static consteval bool _Is_indirection_nothrow(index_sequence<_Indices...>) noexcept { + _NODISCARD static _CONSTEVAL bool _Is_indirection_nothrow(index_sequence<_Indices...>) noexcept { return noexcept(_STD invoke(_STD declval<_Maybe_const<_Const, _Fn>&>(), *_STD get<_Indices>(_STD declval&>()._Current)...)); } @@ -9577,6 +9577,604 @@ namespace ranges { _EXPORT_STD inline constexpr _Adjacent_transform_fn<2> pairwise_transform; } // namespace views + template <_Integer_like _Int> + _NODISCARD constexpr bool _Add_with_overflow_check(const _Int _Left, const _Int _Right, _Int& _Out) { +#ifdef __clang__ + if constexpr (integral<_Int>) { + return __builtin_add_overflow(_Left, _Right, &_Out); + } else +#endif // __clang__ + { + if constexpr (!_Signed_integer_like<_Int>) { + _Out = static_cast<_Int>(_Left + _Right); + return _Out < _Left || _Out < _Right; + } else { + using _UInt = _Make_unsigned_like_t<_Int>; + _Out = static_cast<_Int>(static_cast<_UInt>(_Left) + static_cast<_UInt>(_Right)); + return (_Left > 0 && _Right > 0 && _Out <= 0) || (_Left < 0 && _Right < 0 && _Out >= 0); + } + } + } + + template <_Integer_like _Int> + _NODISCARD constexpr bool _Multiply_with_overflow_check(const _Int _Left, const _Int _Right, _Int& _Out) { +#ifdef __clang__ + if constexpr (integral<_Int>) { + return __builtin_mul_overflow(_Left, _Right, &_Out); + } else +#endif // __clang__ + { + if constexpr (!_Signed_integer_like<_Int>) { + _Out = static_cast<_Int>(_Left * _Right); + return _Left != 0 && _Right > (numeric_limits<_Int>::max)() / _Left; + } else { + // vvv Based on llvm::MulOverflow vvv + // https://github.com/llvm/llvm-project/blob/88e5206/llvm/include/llvm/Support/MathExtras.h#L725-L750 + //===----------------------------------------------------------------------===// + // + // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + // See https://llvm.org/LICENSE.txt for license information. + // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + // + //===----------------------------------------------------------------------===// + using _UInt = _Make_unsigned_like_t<_Int>; + const _UInt _ULeft = _Left < 0 ? (0 - static_cast<_UInt>(_Left)) : static_cast<_UInt>(_Left); + const _UInt _URight = _Right < 0 ? (0 - static_cast<_UInt>(_Right)) : static_cast<_UInt>(_Right); + const _UInt _UResult = static_cast<_UInt>(_ULeft * _URight); + + const bool _Negative = (_Left < 0) != (_Right < 0); + _Out = static_cast<_Int>(_Negative ? (0 - _UResult) : _UResult); + if (_ULeft == 0 || _URight == 0) { + return false; + } + + if (_Negative) { + return _ULeft > (static_cast<_UInt>((numeric_limits<_Int>::max)()) + _UInt{1}) / _URight; + } else { + return _ULeft > static_cast<_UInt>((numeric_limits<_Int>::max)()) / _URight; + } + // ^^^ Based on llvm::MulOverflow ^^^ + } + } + } + + template + concept _Cartesian_product_is_random_access = (random_access_range<_Maybe_const<_Const, _First>> && ... + && (random_access_range<_Maybe_const<_Const, _Rest>> + && sized_range<_Maybe_const<_Const, _Rest>>) ); + + template + concept _Cartesian_product_common_arg = common_range<_Rng> || (sized_range<_Rng> && random_access_range<_Rng>); + + template + concept _Cartesian_product_is_bidirectional = (bidirectional_range<_Maybe_const<_Const, _First>> && ... + && (bidirectional_range<_Maybe_const<_Const, _Rest>> + && _Cartesian_product_common_arg<_Maybe_const<_Const, _Rest>>) ); + + template + concept _Cartesian_product_is_common = _Cartesian_product_common_arg<_First>; + + template + concept _Cartesian_product_is_sized = (sized_range<_Rngs> && ...); + + template class _FirstSent, class _First, class... _Rest> + concept _Cartesian_is_sized_sentinel = + (sized_sentinel_for<_FirstSent<_Maybe_const<_Const, _First>>, iterator_t<_Maybe_const<_Const, _First>>> && ... + && (sized_range<_Maybe_const<_Const, _Rest>> + && sized_sentinel_for>, + iterator_t<_Maybe_const<_Const, _Rest>>>) ); + + template <_Cartesian_product_common_arg _Rng> + _NODISCARD constexpr auto _Cartesian_common_arg_end(_Rng& _Range) { + if constexpr (common_range<_Rng>) { + return _RANGES end(_Range); + } else { + return _RANGES begin(_Range) + _RANGES distance(_Range); + } + } + + template + inline constexpr auto _Compile_time_max_size = (numeric_limits>::max)(); + + template + concept _Constant_sized_range = + sized_range<_Ty> && requires { typename _Require_constant::size()>; }; + + template <_Constant_sized_range _Ty> + inline constexpr auto _Compile_time_max_size<_Ty> = remove_reference_t<_Ty>::size(); + + template + inline constexpr auto _Compile_time_max_size> = _Size; + + template + inline constexpr auto _Compile_time_max_size> = _Size; + + template + requires (_Extent != dynamic_extent) + inline constexpr auto _Compile_time_max_size> = _Extent; + + template + requires (_Extent != dynamic_extent) + inline constexpr auto _Compile_time_max_size> = _Extent; + + template + inline constexpr auto _Compile_time_max_size> = _Compile_time_max_size<_Ty>; + + template + requires sized_range + inline constexpr auto _Compile_time_max_size> = _Compile_time_max_size; + + template + inline constexpr auto _Compile_time_max_size> = _Compile_time_max_size<_Ty>; + + template + requires sized_range + inline constexpr auto _Compile_time_max_size> = _Compile_time_max_size; + + template + _NODISCARD _CONSTEVAL int _Cartesian_product_max_size_bit_width() noexcept { + if constexpr (sized_range<_Rng>) { + if constexpr (requires(range_size_t<_Rng> _Val) { _STD bit_width(_Val); }) { + return _STD bit_width(_Compile_time_max_size<_Rng>); + } else { + return numeric_limits>::digits; + } + } else { + return numeric_limits<_Make_unsigned_like_t>>::digits; + } + } + + template + _NODISCARD _CONSTEVAL auto _Cartesian_product_optimal_size_type() noexcept { + constexpr int _Optimal_size_type_bit_width = + (_Cartesian_product_max_size_bit_width<_First>() + ... + _Cartesian_product_max_size_bit_width<_Rest>()); + if constexpr (_Optimal_size_type_bit_width <= 8) { + return uint_least8_t{}; + } else if constexpr (_Optimal_size_type_bit_width <= 16) { + return uint_least16_t{}; + } else if constexpr (_Optimal_size_type_bit_width <= 32) { + return uint_least32_t{}; + } else if constexpr (_Optimal_size_type_bit_width <= 64) { + return uint_least64_t{}; + } else { + return _Unsigned128{}; + } + } + + _EXPORT_STD template + requires (view<_First> && ... && view<_Rest>) + class cartesian_product_view : public view_interface> { + private: + template + using _Optimal_size_type = decltype(_Cartesian_product_optimal_size_type<_Maybe_const<_Const, _First>, + _Maybe_const<_Const, _Rest>...>()); + + template + using _Difference_type = common_type_t<_Make_signed_like_t<_Optimal_size_type<_Const>>, + range_difference_t<_Maybe_const<_Const, _First>>, range_difference_t<_Maybe_const<_Const, _Rest>>...>; + + template + using _Size_type = common_type_t<_Optimal_size_type<_Const>, range_size_t<_Maybe_const<_Const, _First>>, + range_size_t<_Maybe_const<_Const, _Rest>>...>; + + /* [[no_unique_address]] */ tuple<_First, _Rest...> _Bases; + + template + class _Iterator { + private: + friend cartesian_product_view; + using _Parent_t = _Maybe_const<_Const, cartesian_product_view>; + + _Parent_t* _Parent = nullptr; + tuple>, iterator_t<_Maybe_const<_Const, _Rest>>...> _Current; + + template + constexpr void _Next() { + auto& _It = _STD get<_Index>(_Current); + ++_It; + if constexpr (_Index > 0) { + auto& _Range = _STD get<_Index>(_Parent->_Bases); + if (_It == _RANGES end(_Range)) { + _It = _RANGES begin(_Range); + _Next<_Index - 1>(); + } + } + } + + template + constexpr void _Prev() { + auto& _It = _STD get<_Index>(_Current); + if constexpr (_Index > 0) { + auto& _Range = _STD get<_Index>(_Parent->_Bases); + if (_It == _RANGES begin(_Range)) { + _It = _Cartesian_common_arg_end(_Range); + _Prev<_Index - 1>(); + } + } + --_It; + } + + template + _NODISCARD constexpr bool _Entire_tail_at_begin(index_sequence<_Indices...>) const { + return ((_STD get<_Indices + 1>(_Current) == _RANGES begin(_STD get<_Indices + 1>(_Parent->_Bases))) + && ...); + } + + template + constexpr void _Advance(const _Difference_type<_Const> _Off) { + if (_Off == 0) { + return; + } + + auto& _Range = _STD get<_Index>(_Parent->_Bases); + auto& _It = _STD get<_Index>(_Current); + using _Iter = remove_reference_t; + using _Diff = _Difference_type<_Const>; + + if constexpr (_Index > 0) { + const auto _Size = static_cast<_Diff>(_RANGES ssize(_Range)); + const auto _Begin = _RANGES begin(_Range); + const auto _It_off = static_cast<_Diff>(_It - _Begin); + const auto _It_new_off = static_cast<_Diff>((_It_off + _Off) % _Size); + const auto _Next_off = static_cast<_Diff>((_It_off + _Off) / _Size); + if (_It_new_off < 0) { + _It = _Begin + static_cast>(_It_new_off + _Size); + _Advance<_Index - 1>(_Next_off - 1); + } else { + _It = _Begin + static_cast>(_It_new_off); + _Advance<_Index - 1>(_Next_off); + } + } else { +#if _ITERATOR_DEBUG_LEVEL != 0 + const auto _It_off = static_cast<_Diff>(_It - _RANGES begin(_Range)); + _STL_VERIFY(_It_off + _Off >= 0, "Cannot advance cartesian_product_view iterator before begin " + "(N4928 [range.cartesian.iterator]/19)."); + if constexpr (sized_range) { + const auto _Size = static_cast<_Diff>(_RANGES ssize(_Range)); + _STL_VERIFY(_It_off + _Off < _Size + || (_It_off + _Off == _Size + && _Entire_tail_at_begin(make_index_sequence{})), + "Cannot advance cartesian_product_view iterator past end (N4928 " + "[range.cartesian.iterator]/19)."); + } +#endif // _ITERATOR_DEBUG_LEVEL != 0 + _It += static_cast>(_Off); + } + } + + template + _NODISCARD constexpr bool _Is_end(index_sequence<_Indices...>) const { + return ((_STD get<_Indices>(_Current) == _RANGES end(_STD get<_Indices>(_Parent->_Bases))) || ...); + } + + template + _NODISCARD constexpr auto _End_tuple(index_sequence<_Indices...>) const { + return tuple{_RANGES end(_STD get<0>(_Parent->_Bases)), + _RANGES begin(_STD get<_Indices + 1>(_Parent->_Bases))...}; + } + + template + _NODISCARD constexpr _Difference_type<_Const> _Distance_from(const _Tuple& _Tpl) const { + const auto _Diff = + static_cast<_Difference_type<_Const>>(_STD get<_Index>(_Current) - _STD get<_Index>(_Tpl)); + if constexpr (_Index > 0) { + _Difference_type<_Const> _Result{1}; + const auto _Size = + static_cast<_Difference_type<_Const>>(_RANGES size(_STD get<_Index>(_Parent->_Bases))); + [[maybe_unused]] const bool _Overflow = + _Multiply_with_overflow_check(_Size, _Distance_from<_Index - 1>(_Tpl), _Result) + || _Add_with_overflow_check(_Result, _Diff, _Result); +#if _ITERATOR_DEBUG_LEVEL != 0 + _STL_VERIFY(!_Overflow, "Scaled-sum cannot be represented by difference_type (N4928 " + "[range.cartesian.iterator]/8)."); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + return _Result; + } else { + return _Diff; + } + } + + template + _NODISCARD static _CONSTEVAL bool _Is_iter_move_nothrow(index_sequence<_Indices...>) noexcept { + return conjunction_v< + is_nothrow_move_constructible>>, + is_nothrow_move_constructible>>...> + && (noexcept(_RANGES iter_move(_STD get<_Indices>(_STD declval()._Current))) + && ...); + } + + template + _NODISCARD static _CONSTEVAL bool _Is_iter_swap_nothrow(index_sequence<_Indices...>) noexcept { + return (noexcept(_RANGES iter_swap(_STD get<_Indices>(_STD declval()._Current), + _STD get<_Indices>(_STD declval()._Current))) + && ...); + } + + constexpr _Iterator(_Parent_t& _Parent_, + tuple>, iterator_t<_Maybe_const<_Const, _Rest>>...> _Current_) + : _Parent(_STD addressof(_Parent_)), _Current(_STD move(_Current_)) {} + + public: + using iterator_category = input_iterator_tag; + using iterator_concept = conditional_t<_Cartesian_product_is_random_access<_Const, _First, _Rest...>, + random_access_iterator_tag, + conditional_t<_Cartesian_product_is_bidirectional<_Const, _First, _Rest...>, bidirectional_iterator_tag, + conditional_t>, forward_iterator_tag, + input_iterator_tag>>>; + using value_type = + tuple>, range_value_t<_Maybe_const<_Const, _Rest>>...>; + using reference = tuple>, + range_reference_t<_Maybe_const<_Const, _Rest>>...>; + using difference_type = _Difference_type<_Const>; + + _Iterator() = default; + + constexpr _Iterator(_Iterator _It) + requires _Const + && (convertible_to, iterator_t> && ... + && convertible_to, iterator_t>) + : _Parent(_It._Parent), _Current(_STD move(_It._Current)) {} + + _NODISCARD constexpr auto operator*() const { + return _RANGES _Tuple_transform([](auto& _It) -> decltype(auto) { return *_It; }, _Current); + } + + constexpr _Iterator& operator++() { + _Next(); + return *this; + } + + constexpr void operator++(int) { + ++*this; + } + + constexpr _Iterator operator++(int) + requires forward_range<_Maybe_const<_Const, _First>> + { + auto _Tmp = *this; + ++*this; + return _Tmp; + } + + constexpr _Iterator& operator--() + requires _Cartesian_product_is_bidirectional<_Const, _First, _Rest...> + { + _Prev(); + return *this; + } + + constexpr _Iterator operator--(int) + requires _Cartesian_product_is_bidirectional<_Const, _First, _Rest...> + { + auto _Tmp = *this; + --*this; + return _Tmp; + } + + constexpr _Iterator& operator+=(const difference_type _Off) + requires _Cartesian_product_is_random_access<_Const, _First, _Rest...> + { + _Advance(_Off); + return *this; + } + + constexpr _Iterator& operator-=(const difference_type _Off) + requires _Cartesian_product_is_random_access<_Const, _First, _Rest...> + { + _Advance(-_Off); + return *this; + } + + _NODISCARD constexpr reference operator[](const difference_type _Off) const + requires _Cartesian_product_is_random_access<_Const, _First, _Rest...> + { + return *(*this + _Off); + } + + _NODISCARD_FRIEND constexpr bool operator==(const _Iterator& _Left, const _Iterator& _Right) + requires equality_comparable>> + { + return _Left._Current == _Right._Current; + } + + _NODISCARD_FRIEND constexpr bool operator==(const _Iterator& _It, default_sentinel_t) { + return _It._Is_end(make_index_sequence<1 + sizeof...(_Rest)>{}); + } + + _NODISCARD_FRIEND constexpr auto operator<=>(const _Iterator& _Left, const _Iterator& _Right) + requires _All_random_access<_Const, _First, _Rest...> + { + return _Left._Current <=> _Right._Current; + } + + _NODISCARD_FRIEND constexpr _Iterator operator+(const _Iterator& _It, const difference_type _Off) + requires _Cartesian_product_is_random_access<_Const, _First, _Rest...> + { + return _Iterator{_It} += _Off; + } + + _NODISCARD_FRIEND constexpr _Iterator operator+(const difference_type _Off, const _Iterator& _It) + requires _Cartesian_product_is_random_access<_Const, _First, _Rest...> + { + return _It + _Off; + } + + _NODISCARD_FRIEND constexpr _Iterator operator-(const _Iterator& _It, const difference_type _Off) + requires _Cartesian_product_is_random_access<_Const, _First, _Rest...> + { + return _Iterator{_It} -= _Off; + } + + _NODISCARD_FRIEND constexpr difference_type operator-(const _Iterator& _Left, const _Iterator& _Right) + requires _Cartesian_is_sized_sentinel<_Const, iterator_t, _First, _Rest...> + { + return _Left._Distance_from(_Right._Current); + } + + _NODISCARD_FRIEND constexpr difference_type operator-(const _Iterator& _It, default_sentinel_t) + requires _Cartesian_is_sized_sentinel<_Const, sentinel_t, _First, _Rest...> + { + return _It._Distance_from(_It._End_tuple(make_index_sequence{})); + } + + _NODISCARD_FRIEND constexpr difference_type operator-(default_sentinel_t _Se, const _Iterator& _It) + requires _Cartesian_is_sized_sentinel<_Const, sentinel_t, _First, _Rest...> + { + return -(_It - _Se); + } + + _NODISCARD_FRIEND constexpr auto iter_move(const _Iterator& _It) noexcept( + _Is_iter_move_nothrow(make_index_sequence<1 + sizeof...(_Rest)>{})) { + return _RANGES _Tuple_transform(_RANGES iter_move, _It._Current); + } + + friend constexpr void iter_swap(const _Iterator& _Left, const _Iterator& _Right) noexcept( + _Is_iter_swap_nothrow(make_index_sequence<1 + sizeof...(_Rest)>{})) + requires (indirectly_swappable>> && ... + && indirectly_swappable>>) + { + return [&](index_sequence<_Indices...>) { + return (_RANGES iter_swap(_STD get<_Indices>(_Left._Current), _STD get<_Indices>(_Right._Current)), + ...); + } + (make_index_sequence<1 + sizeof...(_Rest)>{}); + } + }; + + template + _NODISCARD constexpr auto _Begin_or_first_end([[maybe_unused]] const bool _Is_empty) { + if constexpr (_Index == 0) { + return _Is_empty ? _RANGES begin(_STD get<_Index>(_Bases)) + : _Cartesian_common_arg_end(_STD get<_Index>(_Bases)); + } else { + return _RANGES begin(_STD get<_Index>(_Bases)); + } + } + + template + _NODISCARD constexpr auto _Begin_or_first_end([[maybe_unused]] const bool _Is_empty) const { + if constexpr (_Index == 0) { + return _Is_empty ? _RANGES begin(_STD get<_Index>(_Bases)) + : _Cartesian_common_arg_end(_STD get<_Index>(_Bases)); + } else { + return _RANGES begin(_STD get<_Index>(_Bases)); + } + } + + public: + constexpr cartesian_product_view() = default; + + constexpr explicit cartesian_product_view(_First _First_base, _Rest... _Other_bases) noexcept( + (is_nothrow_move_constructible_v<_First> && ... && is_nothrow_move_constructible_v<_Rest>) ) // strengthened + : _Bases(_STD move(_First_base), _STD move(_Other_bases)...) {} + + _NODISCARD constexpr _Iterator begin() + requires (!_Simple_view<_First> || ... || !_Simple_view<_Rest>) + { + return _Iterator{*this, _RANGES _Tuple_transform(_RANGES begin, _Bases)}; + } + + _NODISCARD constexpr _Iterator begin() const + requires (range && ... && range) + { + return _Iterator{*this, _RANGES _Tuple_transform(_RANGES begin, _Bases)}; + } + + _NODISCARD constexpr _Iterator end() + requires ((!_Simple_view<_First> || ... || !_Simple_view<_Rest>) && _Cartesian_product_is_common<_First>) + { + const bool _Is_empty = [&](index_sequence<_Indices...>) { + return (_RANGES empty(_STD get<_Indices + 1>(_Bases)) || ...); + } + (make_index_sequence{}); + + const auto _Make_iter_tuple = [&](index_sequence<_Indices...>) { + return tuple{_Begin_or_first_end<_Indices>(_Is_empty)...}; + }; + return _Iterator{*this, _Make_iter_tuple(make_index_sequence<1 + sizeof...(_Rest)>{})}; + } + + _NODISCARD constexpr _Iterator end() const + requires _Cartesian_product_is_common + { + const bool _Is_empty = [&](index_sequence<_Indices...>) { + return (_RANGES empty(_STD get<_Indices + 1>(_Bases)) || ...); + } + (make_index_sequence{}); + + const auto _Make_iter_tuple = [&](index_sequence<_Indices...>) { + return tuple{_Begin_or_first_end<_Indices>(_Is_empty)...}; + }; + return _Iterator{*this, _Make_iter_tuple(make_index_sequence<1 + sizeof...(_Rest)>{})}; + } + + _NODISCARD constexpr default_sentinel_t end() const noexcept { + return default_sentinel; + } + + _NODISCARD constexpr auto size() + requires _Cartesian_product_is_sized<_First, _Rest...> + { + return [&](index_sequence<_Indices...>) { +#if _CONTAINER_DEBUG_LEVEL > 0 + _Size_type _Product{1}; + const bool _Overflow = + (_Multiply_with_overflow_check( + _Product, static_cast<_Size_type>(_RANGES size(_STD get<_Indices>(_Bases))), _Product) + || ...); + _STL_VERIFY(!_Overflow, "Size of cartesian product cannot be represented by size type (N4928 " + "[range.cartesian.view]/10)."); + return _Product; +#else // ^^^ _CONTAINER_DEBUG_LEVEL > 0 / _CONTAINER_DEBUG_LEVEL == 0 vvv + return (static_cast<_Size_type>(_RANGES size(_STD get<_Indices>(_Bases))) * ...); +#endif // ^^^ _CONTAINER_DEBUG_LEVEL == 0 ^^^ + } + (make_index_sequence<1 + sizeof...(_Rest)>{}); + } + + _NODISCARD constexpr auto size() const + requires _Cartesian_product_is_sized + { + return [&](index_sequence<_Indices...>) { +#if _CONTAINER_DEBUG_LEVEL > 0 + _Size_type _Product{1}; + const bool _Overflow = + (_Multiply_with_overflow_check( + _Product, static_cast<_Size_type>(_RANGES size(_STD get<_Indices>(_Bases))), _Product) + || ...); + _STL_VERIFY(!_Overflow, "Size of cartesian product cannot be represented by size type (N4928 " + "[range.cartesian.view]/10)."); + return _Product; +#else // ^^^ _CONTAINER_DEBUG_LEVEL > 0 / _CONTAINER_DEBUG_LEVEL == 0 vvv + return (static_cast<_Size_type>(_RANGES size(_STD get<_Indices>(_Bases))) * ...); +#endif // ^^^ _CONTAINER_DEBUG_LEVEL == 0 ^^^ + } + (make_index_sequence<1 + sizeof...(_Rest)>{}); + } + }; + + template + cartesian_product_view(_Rngs&&...) -> cartesian_product_view...>; + + namespace views { + class _Cartesian_product_fn { + public: + _NODISCARD constexpr auto operator()() const noexcept { + return views::single(tuple{}); + } + + template + _NODISCARD constexpr auto operator()(_Rngs&&... _Ranges) const + noexcept(noexcept(cartesian_product_view...>{_STD forward<_Rngs>(_Ranges)...})) + requires requires { cartesian_product_view...>{_STD forward<_Rngs>(_Ranges)...}; } + { + return cartesian_product_view...>{_STD forward<_Rngs>(_Ranges)...}; + } + }; + + _EXPORT_STD inline constexpr _Cartesian_product_fn cartesian_product; + } // 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 8f652b7a97..893f73b9d0 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -340,6 +340,7 @@ // P2302R4 ranges::contains, ranges::contains_subrange // P2321R2 zip // P2322R6 ranges::fold_left, ranges::fold_right, Etc. +// P2374R4 views::cartesian_product // P2387R3 Pipe Support For User-Defined Range Adaptors // P2404R3 Move-Only Types For Comparison Concepts // P2417R2 More constexpr bitset @@ -357,6 +358,7 @@ // P2499R0 string_view Range Constructor Should Be explicit // P2505R5 Monadic Functions For expected // P2539R4 Synchronizing print() With The Underlying Stream +// P2540R1 Empty Product For Certain Views // P2549R1 unexpected::error() // P2652R2 Disallowing User Specialization Of allocator_traits @@ -1719,24 +1721,25 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect #define __cpp_lib_move_only_function 202110L #ifdef __cpp_lib_concepts -#define __cpp_lib_out_ptr 202106L -#define __cpp_lib_print 202207L -#define __cpp_lib_ranges_as_const 202207L -#define __cpp_lib_ranges_as_rvalue 202207L -#define __cpp_lib_ranges_chunk 202202L -#define __cpp_lib_ranges_chunk_by 202202L -#define __cpp_lib_ranges_contains 202207L -#define __cpp_lib_ranges_enumerate 202302L -#define __cpp_lib_ranges_find_last 202207L -#define __cpp_lib_ranges_fold 202207L -#define __cpp_lib_ranges_iota 202202L -#define __cpp_lib_ranges_join_with 202202L -#define __cpp_lib_ranges_repeat 202207L -#define __cpp_lib_ranges_slide 202202L -#define __cpp_lib_ranges_starts_ends_with 202106L -#define __cpp_lib_ranges_stride 202207L -#define __cpp_lib_ranges_to_container 202202L -#define __cpp_lib_ranges_zip 202110L +#define __cpp_lib_out_ptr 202106L +#define __cpp_lib_print 202207L +#define __cpp_lib_ranges_as_const 202207L +#define __cpp_lib_ranges_as_rvalue 202207L +#define __cpp_lib_ranges_cartesian_product 202207L +#define __cpp_lib_ranges_chunk 202202L +#define __cpp_lib_ranges_chunk_by 202202L +#define __cpp_lib_ranges_contains 202207L +#define __cpp_lib_ranges_enumerate 202302L +#define __cpp_lib_ranges_find_last 202207L +#define __cpp_lib_ranges_fold 202207L +#define __cpp_lib_ranges_iota 202202L +#define __cpp_lib_ranges_join_with 202202L +#define __cpp_lib_ranges_repeat 202207L +#define __cpp_lib_ranges_slide 202202L +#define __cpp_lib_ranges_starts_ends_with 202106L +#define __cpp_lib_ranges_stride 202207L +#define __cpp_lib_ranges_to_container 202202L +#define __cpp_lib_ranges_zip 202110L #endif // __cpp_lib_concepts #define __cpp_lib_spanstream 202106L diff --git a/tests/std/test.lst b/tests/std/test.lst index ccede7b159..ea1f5fda4d 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -576,6 +576,8 @@ tests\P2321R2_views_adjacent_transform tests\P2321R2_views_zip tests\P2321R2_views_zip_transform tests\P2322R6_ranges_alg_fold +tests\P2374R4_views_cartesian_product +tests\P2374R4_views_cartesian_product_death tests\P2387R3_bind_back tests\P2387R3_pipe_support_for_user_defined_range_adaptors tests\P2401R0_conditional_noexcept_for_exchange diff --git a/tests/std/tests/P2374R4_views_cartesian_product/env.lst b/tests/std/tests/P2374R4_views_cartesian_product/env.lst new file mode 100644 index 0000000000..8ac7033b20 --- /dev/null +++ b/tests/std/tests/P2374R4_views_cartesian_product/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\strict_concepts_latest_matrix.lst diff --git a/tests/std/tests/P2374R4_views_cartesian_product/test.cpp b/tests/std/tests/P2374R4_views_cartesian_product/test.cpp new file mode 100644 index 0000000000..1c9f016d17 --- /dev/null +++ b/tests/std/tests/P2374R4_views_cartesian_product/test.cpp @@ -0,0 +1,1032 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using ranges::bidirectional_range, ranges::random_access_range, ranges::sized_range, ranges::common_range, + ranges::iterator_t; + +template +using maybe_const = conditional_t; + +// Helper concepts from [range.cartesian.view] +template +concept CartesianProductIsRandomAccess = (random_access_range> && ... + && (random_access_range> + && sized_range>) ); + +template +concept CartesianProductCommonArg = common_range || (sized_range && random_access_range); + +template +concept CartesianProductIsBidirectional = (bidirectional_range> && ... + && (bidirectional_range> + && CartesianProductCommonArg>) ); + +template +concept CartesianProductIsCommon = CartesianProductCommonArg; + +template +concept CartesianProductIsSized = (sized_range && ...); + +template class FirstSent, class First, class... Rest> +concept CartesianIsSizedSentinel = + (sized_sentinel_for>, iterator_t>> && ... + && (sized_range> + && sized_sentinel_for>, iterator_t>>) ); + +template +concept CanViewCartesianProduct = requires(Ranges&&... rs) { views::cartesian_product(forward(rs)...); }; + +template +concept UnsignedIntegerLike = _Integer_like && (!_Signed_integer_like); + +template +constexpr bool is_iter_move_nothrow() { + constexpr bool is_inner_iter_move_nothrow = + (noexcept(ranges::iter_move(declval&>())) + && ... // + && noexcept(ranges::iter_move(declval&>()))); + constexpr bool are_references_nothrow_movable = + conjunction_v>, + is_nothrow_move_constructible>...>; + return is_inner_iter_move_nothrow && are_references_nothrow_movable; +} + +template +constexpr bool is_iter_swap_nothrow() { + return (noexcept(ranges::iter_swap(declval&>(), declval&>())) + && ... // + && noexcept(ranges::iter_swap(declval&>(), declval&>()))); +} + +template +constexpr bool test_one(Expected&& expected_range, First&& first, Rest&&... rest) { + using ranges::cartesian_product_view, ranges::view, ranges::input_range, ranges::forward_range, ranges::range, + ranges::range_value_t, ranges::range_reference_t, ranges::range_rvalue_reference_t, ranges::range_difference_t, + ranges::sentinel_t, ranges::prev, ranges::const_iterator_t, ranges::const_sentinel_t; + using views::all_t; + + using VFirst = all_t; + using R = cartesian_product_view...>; + + constexpr bool is_view = (view> && ... && view>); + constexpr bool all_copy_constructible = (copy_constructible && ... && copy_constructible>); + constexpr bool is_bidirectional = CartesianProductIsBidirectional...>; + constexpr bool is_const_bidirectional = CartesianProductIsBidirectional...>; + constexpr bool is_random_access = CartesianProductIsRandomAccess...>; + constexpr bool is_const_random_access = CartesianProductIsRandomAccess...>; + constexpr bool is_sized = CartesianProductIsSized...>; + constexpr bool is_const_sized = CartesianProductIsSized...>; + constexpr bool is_common = CartesianProductIsCommon...>; + constexpr bool is_const_common = CartesianProductIsCommon...>; + + STATIC_ASSERT(view); + STATIC_ASSERT(input_range); + STATIC_ASSERT(forward_range == forward_range); + STATIC_ASSERT(bidirectional_range == is_bidirectional); + STATIC_ASSERT(random_access_range == is_random_access); + STATIC_ASSERT(!ranges::contiguous_range); + STATIC_ASSERT(sized_range == is_sized); + STATIC_ASSERT(common_range == is_common); + + // Check non-default-initializability + STATIC_ASSERT(is_default_constructible_v + == (is_default_constructible_v && ... && is_default_constructible_v>) ); + + // Check borrowed_range + static_assert(!ranges::borrowed_range); + + // Check range closure object + constexpr auto closure = views::cartesian_product; + + // ... with lvalue argument + STATIC_ASSERT(CanViewCartesianProduct + == (!is_view || (copy_constructible && ... && copy_constructible>) )); + if constexpr (CanViewCartesianProduct) { + constexpr bool is_noexcept = + !is_view + || (is_nothrow_copy_constructible_v && ... && is_nothrow_copy_constructible_v>); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(closure(first, rest...)) == is_noexcept); + } + + // ... with const lvalue argument + STATIC_ASSERT(CanViewCartesianProduct&, const remove_reference_t&...> + == (!is_view || (copy_constructible && ... && copy_constructible>) )); + if constexpr (CanViewCartesianProduct&, const remove_reference_t&...>) { + using RC = + cartesian_product_view&>, all_t&>...>; + constexpr bool is_noexcept = + !is_view + || (is_nothrow_copy_constructible_v && ... && is_nothrow_copy_constructible_v>); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(closure(as_const(first), as_const(rest)...)) == is_noexcept); + } + + // ... with rvalue argument + STATIC_ASSERT(CanViewCartesianProduct, remove_reference_t...> + == (is_view || (movable> && ... && movable>) )); + if constexpr (CanViewCartesianProduct, remove_reference_t...>) { + using RS = cartesian_product_view>, all_t>...>; + constexpr bool is_noexcept = + (is_nothrow_move_constructible_v && ... && is_nothrow_move_constructible_v>); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(closure(move(first), move(rest)...)) == is_noexcept); + } + + // ... with const rvalue argument + STATIC_ASSERT(CanViewCartesianProduct, const remove_reference_t...> + == (is_view && (copy_constructible && ... && copy_constructible>) )); + if constexpr (CanViewCartesianProduct, const remove_reference_t...>) { + constexpr bool is_noexcept = + (is_nothrow_copy_constructible_v && ... && is_nothrow_copy_constructible_v>); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(closure(move(as_const(first)), move(as_const(rest))...)) == is_noexcept); + } + + // Check deduction guide + same_as auto r = cartesian_product_view{forward(first), forward(rest)...}; + + // Check cartesian_product_view::size + STATIC_ASSERT(CanMemberSize == is_sized); + if constexpr (CanMemberSize) { + UnsignedIntegerLike auto s = r.size(); + assert(s == ranges::size(expected_range)); + } + + // Check cartesian_product_view::size (const) + STATIC_ASSERT(CanMemberSize == is_const_sized); + if constexpr (CanMemberSize) { + UnsignedIntegerLike auto s = as_const(r).size(); + assert(s == ranges::size(expected_range)); + } + + const bool is_empty = ranges::empty(expected_range); + + // Check view_interface::empty and operator bool + STATIC_ASSERT(CanMemberEmpty == (forward_range || is_sized)); + STATIC_ASSERT(CanBool == CanEmpty); + if constexpr (CanMemberEmpty) { + assert(r.empty() == is_empty); + assert(static_cast(r) == !is_empty); + } + + // Check view_interface::empty and operator bool (const) + STATIC_ASSERT(CanMemberEmpty == (forward_range || is_const_sized)); + STATIC_ASSERT(CanBool == CanEmpty); + if constexpr (CanMemberEmpty) { + assert(as_const(r).empty() == is_empty); + assert(static_cast(as_const(r)) == !is_empty); + } + + assert(ranges::equal(r, expected_range)); + if (!forward_range) { // intentionally not if constexpr + return true; + } + + // Check cartesian_product_view::begin + STATIC_ASSERT(CanMemberBegin); + { + const same_as> auto i = r.begin(); + if (!is_empty) { + assert(*i == *begin(expected_range)); + } + + if constexpr (all_copy_constructible) { + auto r2 = r; + const same_as> auto i2 = r2.begin(); + if (!is_empty) { + assert(*i2 == *i); + } + } + } + + // Check cartesian_product_view::begin (const) + STATIC_ASSERT(CanMemberBegin == (range && ... && range>) ); + if constexpr (CanMemberBegin) { + const same_as> auto ci = as_const(r).begin(); + if (!is_empty) { + assert(*ci == *begin(expected_range)); + } + + if constexpr (all_copy_constructible) { + const auto r2 = r; + const same_as> auto ci2 = r2.begin(); + if (!is_empty) { + assert(*ci2 == *ci); + } + } + } + + // Check cartesian_product_view::end + STATIC_ASSERT(CanMemberEnd); + { + const same_as> auto s = r.end(); + assert((r.begin() == s) == is_empty); + STATIC_ASSERT(common_range == is_common); + + if constexpr (same_as, default_sentinel_t>) { + STATIC_ASSERT(!is_common); + STATIC_ASSERT(noexcept(r.end())); + } else if constexpr (common_range && is_bidirectional) { + if (!is_empty) { + assert(*prev(s) == *prev(end(expected_range))); + } + + if constexpr (all_copy_constructible) { + auto r2 = r; + if (!is_empty) { + assert(*prev(r2.end()) == *prev(end(expected_range))); + } + } + } + } + + // Check cartesian_product_view::end (const) + STATIC_ASSERT(CanMemberEnd); + if constexpr (CanMemberEnd) { + const same_as> auto cs = as_const(r).end(); + assert((r.begin() == cs) == is_empty); + STATIC_ASSERT(common_range == is_const_common); + + if constexpr (same_as, default_sentinel_t>) { + STATIC_ASSERT(!is_const_common); + STATIC_ASSERT(noexcept(as_const(r).end())); + } else if constexpr (common_range && is_const_bidirectional) { + if (!is_empty) { + assert(*prev(cs) == *prev(end(expected_range))); + } + + if constexpr (all_copy_constructible) { + const auto r2 = r; + if (!is_empty) { + assert(*prev(r2.end()) == *prev(end(expected_range))); + } + } + } + } + + // Check view_interface::cbegin + STATIC_ASSERT(CanMemberCBegin); + STATIC_ASSERT(CanMemberCBegin == (range && ... && range>) ); + { + const same_as> auto i = r.cbegin(); + if (!is_empty) { + assert(*i == *cbegin(expected_range)); + } + + if constexpr (all_copy_constructible) { + auto r2 = r; + const same_as> auto i2 = r2.cbegin(); + if (!is_empty) { + assert(*i2 == *i); + } + } + + if constexpr (CanCBegin) { + const same_as> auto i3 = as_const(r).cbegin(); + if (!is_empty) { + assert(*i3 == *i); + } + } + } + + // Check view_interface::cend + STATIC_ASSERT(CanMemberCEnd); + STATIC_ASSERT(CanMemberCEnd); + if (!is_empty) { + same_as> auto i = r.cend(); + if constexpr (common_range && is_bidirectional) { + assert(*prev(i) == *prev(cend(expected_range))); + } + + if constexpr (CanCEnd) { + same_as> auto i2 = as_const(r).cend(); + if constexpr (common_range && is_const_bidirectional) { + assert(*prev(i2) == *prev(cend(expected_range))); + } + } + } + + if (is_empty) { + return true; + } + + // Check view_interface::data + STATIC_ASSERT(!CanData); + STATIC_ASSERT(!CanData); + + // Check view_interface::operator[] + STATIC_ASSERT(CanIndex == is_random_access); + if constexpr (CanIndex) { + assert(r[0] == expected_range[0]); + } + + // Check view_interface::operator[] (const) + STATIC_ASSERT(CanIndex == is_const_random_access); + if constexpr (CanIndex) { + assert(as_const(r)[0] == expected_range[0]); + } + + // Check view_interface::front + STATIC_ASSERT(CanMemberFront == forward_range); + if constexpr (CanMemberFront) { + assert(r.front() == *begin(expected_range)); + } + + // Check view_interface::front (const) + STATIC_ASSERT(CanMemberFront == forward_range); + if constexpr (CanMemberFront) { + assert(as_const(r).front() == *begin(expected_range)); + } + + // Check view_interface::back + STATIC_ASSERT(CanMemberBack == (is_bidirectional && is_common)); + if constexpr (CanMemberBack) { + assert(r.back() == *prev(end(expected_range))); + } + + // Check view_interface::back (const) + STATIC_ASSERT(CanMemberBack == (is_const_bidirectional && is_const_common)); + if constexpr (CanMemberBack) { + assert(as_const(r).back() == *prev(end(expected_range))); + } + + { // Check cartesian_product_view::iterator + using I = iterator_t; + STATIC_ASSERT(input_iterator); + + // Check iterator_category + STATIC_ASSERT(same_as); + + // Check iterator_concept + using IterConcept = typename I::iterator_concept; + STATIC_ASSERT(is_random_access == same_as); + STATIC_ASSERT((is_bidirectional && !is_random_access) == same_as); + STATIC_ASSERT((forward_range && !is_bidirectional) == same_as); + + // Check value_type + STATIC_ASSERT(same_as, range_value_t...>>); + + // Check default-initializability + STATIC_ASSERT(default_initializable == default_initializable>); + + auto i = r.begin(); + + { // Check dereference + same_as, range_reference_t...>> decltype(auto) v = *as_const(i); + assert(v == expected_range[0]); + } + + { // Check pre-incrementation + same_as decltype(auto) i2 = ++i; + assert(&i2 == &i); + if (i != r.end()) { + assert(*i == expected_range[1]); + } + i = r.begin(); + } + + if constexpr (forward_range) { // Check post-incrementation + same_as decltype(auto) i2 = i++; + assert(*i2 == expected_range[0]); + if (i != r.end()) { + assert(*i == expected_range[1]); + } + i = r.begin(); + } else { + STATIC_ASSERT(is_void_v); + } + + if constexpr (is_bidirectional) { + { // Check pre-decrementation + i = ranges::next(r.begin()); + + same_as decltype(auto) i2 = --i; + assert(&i2 == &i); + assert(*i2 == expected_range[0]); + } + + { // Check post-decrementation + i = ranges::next(r.begin()); + + same_as decltype(auto) i2 = i--; + if (i2 != r.end()) { + assert(*i2 == expected_range[1]); + } + assert(*i == expected_range[0]); + } + } + + if constexpr (is_random_access) { + const auto half_max_distance = ranges::distance(r) / 2; + + { // Check advancement operators + same_as decltype(auto) i2 = (i += half_max_distance); + assert(&i2 == &i); + if (i != r.end()) { + assert(*i == expected_range[static_cast(half_max_distance)]); + } + + same_as decltype(auto) i3 = (i -= half_max_distance); + assert(&i3 == &i); + assert(*i == expected_range[0]); + } + + { // Check subscript operator + same_as, range_reference_t...>> decltype(auto) v = i[0]; + assert(v == expected_range[0]); + } + + if constexpr (equality_comparable>) { // Check equality comparisons + auto i2 = r.begin(); + same_as auto b1 = i == i2; + assert(b1); + ++i2; + same_as auto b2 = i != i2; + assert(b2); + same_as auto b3 = i2 != default_sentinel; + assert(b3); + ranges::advance(i2, r.end()); + same_as auto b4 = i2 == default_sentinel; + assert(b4); + } + + if constexpr ((random_access_range && ... + && random_access_range>) ) { // Check 3way comparisons + using Cat = common_comparison_category_t>, + compare_three_way_result_t>>...>; + auto i2 = r.begin(); + same_as auto cmp1 = i <=> i2; + assert(cmp1 == Cat::equivalent); + ++i2; + assert((i <=> i2) == Cat::less); + assert((i2 <=> i) == Cat::greater); + + same_as auto b1 = i < i2; + assert(b1); + same_as auto b2 = i2 > i; + assert(b2); + same_as auto b3 = i <= i2; + assert(b3); + same_as auto b4 = i2 >= i; + assert(b4); + } + + { // Check operator+ + same_as auto i2 = i + half_max_distance; + if (i2 != r.end()) { + assert(*i2 == expected_range[static_cast(half_max_distance)]); + } + + same_as auto i3 = half_max_distance + i; + if (i3 != r.end()) { + assert(*i3 == expected_range[static_cast(half_max_distance)]); + } + } + + { // Check operator-(Iter, Diff) + same_as auto i2 = (i + half_max_distance) - half_max_distance; + assert(*i2 == expected_range[0]); + } + } + + if constexpr (CartesianIsSizedSentinel...>) { // Check differencing + _Signed_integer_like auto diff = i - i; + assert(diff == 0); + assert(i - ranges::next(i) == -1); + assert(ranges::next(i) - i == 1); + } + + STATIC_ASSERT(sized_sentinel_for + == CartesianIsSizedSentinel...>); + if constexpr (sized_sentinel_for) { // Check differencing with default_sentinel + const auto expected_size = ranges::ssize(expected_range); + const _Signed_integer_like auto diff1 = i - default_sentinel; + assert(diff1 == -expected_size); + const _Signed_integer_like auto diff2 = default_sentinel - i; + assert(diff2 == expected_size); + } + + { // Check iter_move (hidden friend available via ADL) + same_as, range_rvalue_reference_t>...>> decltype(auto) + rval = iter_move(as_const(i)); + assert(rval == expected_range[0]); + static_assert(noexcept(iter_move(i)) == is_iter_move_nothrow...>()); + } + + if constexpr ((indirectly_swappable> && ... + && indirectly_swappable>>) ) { + // Check iter_swap, other tests are defined in test_iter_swap function + static_assert(is_void_v); + static_assert(noexcept(iter_swap(i, i)) == is_iter_swap_nothrow...>()); + } + } + + // Check cartesian_product_view::iterator + if constexpr (CanMemberBegin) { + using CI = iterator_t; + STATIC_ASSERT(input_iterator); + + // Check iterator_category + STATIC_ASSERT(same_as); + + // Check iterator_concept + using IterConcept = typename CI::iterator_concept; + STATIC_ASSERT(is_const_random_access == same_as); + STATIC_ASSERT( + (is_const_bidirectional && !is_const_random_access) == same_as); + STATIC_ASSERT( + (forward_range && !is_const_bidirectional) == same_as); + + // Check value_type + static_assert( + same_as, range_value_t...>>); + + // Check default-initializability + STATIC_ASSERT(default_initializable == default_initializable>); + + // Check conversion from non-const iterator + if constexpr ((convertible_to, iterator_t> && ... + && convertible_to, iterator_t>) ) { + auto i = r.begin(); + [[maybe_unused]] CI ci{move(i)}; + } + + auto i = r.begin(); + CI ci = as_const(r).begin(); + + { // Check dereference + same_as, range_reference_t...>> decltype(auto) v = + *as_const(ci); + assert(v == expected_range[0]); + } + + { // Check pre-incrementation + same_as decltype(auto) ci2 = ++ci; + assert(&ci2 == &ci); + if (ci != as_const(r).end()) { + assert(*ci == expected_range[1]); + } + ci = as_const(r).begin(); + } + + if constexpr (forward_range) { // Check post-incrementation + same_as decltype(auto) ci2 = ci++; + assert(*ci2 == expected_range[0]); + if (ci != as_const(r).end()) { + assert(*ci == expected_range[1]); + } + ci = as_const(r).begin(); + } else { + STATIC_ASSERT(is_void_v); + } + + if constexpr (is_const_bidirectional) { + { // Check pre-decrementation + ci = ranges::next(r.begin()); + + same_as decltype(auto) ci2 = --ci; + assert(&ci2 == &ci); + assert(*ci2 == expected_range[0]); + } + + { // Check post-decrementation + ci = ranges::next(r.begin()); + + same_as decltype(auto) ci2 = ci--; + if (ci2 != r.end()) { + assert(*ci2 == expected_range[1]); + } + assert(*ci == expected_range[0]); + } + } + + if constexpr (is_const_random_access) { + const auto half_max_distance = ranges::distance(r) / 2; + + { // Check advancement operators + same_as decltype(auto) ci2 = (ci += half_max_distance); + assert(&ci2 == &ci); + if (ci != r.end()) { + assert(*ci == expected_range[static_cast(half_max_distance)]); + } + + same_as decltype(auto) ci3 = (ci -= half_max_distance); + assert(&ci3 == &ci); + assert(*ci == expected_range[0]); + } + + { // Check subscript operator + same_as, range_reference_t...>> decltype(auto) v = + ci[0]; + assert(v == expected_range[0]); + } + + if constexpr (equality_comparable>) { // Check equality comparisons + CI ci2 = as_const(r).begin(); + same_as auto b1 = ci == ci2; + assert(b1); + ++ci2; + same_as auto b2 = ci != ci2; + assert(b2); + same_as auto b3 = ci2 != default_sentinel; + assert(b3); + ranges::advance(ci2, r.end()); + same_as auto b4 = ci2 == default_sentinel; + assert(b4); + } + + if constexpr (equality_comparable_with, + iterator_t>) { // Check equality comparisons (mixed) + CI ci2 = as_const(r).begin(); + same_as auto b1 = i == ci2; + assert(b1); + ++ci2; + same_as auto b2 = i != ci2; + assert(b2); + } + + if constexpr ((random_access_range && ... + && random_access_range>) ) { // Check 3way comparisons + using Cat = common_comparison_category_t>, + compare_three_way_result_t>>...>; + CI ci2 = as_const(r).begin(); + same_as auto cmp1 = ci <=> ci2; + assert(cmp1 == Cat::equivalent); + ++ci2; + assert((ci <=> ci2) == Cat::less); + assert((ci2 <=> ci) == Cat::greater); + + same_as auto b1 = ci < ci2; + assert(b1); + same_as auto b2 = ci2 > ci; + assert(b2); + same_as auto b3 = ci <= ci2; + assert(b3); + same_as auto b4 = ci2 >= ci; + assert(b4); + } + + if constexpr ((random_access_range && ... + && random_access_range>) ) { // Check 3way comparisons (mixed) + using Cat = common_comparison_category_t>, + compare_three_way_result_t>>...>; + CI ci2 = as_const(r).begin(); + same_as auto cmp1 = i <=> ci2; + assert(cmp1 == Cat::equivalent); + ++ci2; + assert((i <=> ci2) == Cat::less); + assert((ci2 <=> i) == Cat::greater); + + same_as auto b1 = i < ci2; + assert(b1); + same_as auto b2 = ci2 > i; + assert(b2); + same_as auto b3 = i <= ci2; + assert(b3); + same_as auto b4 = ci2 >= i; + assert(b4); + } + + { // Check operator+ + same_as auto ci2 = ci + 1; + if (ci2 != r.end()) { + assert(*ci2 == expected_range[1]); + } + + same_as auto ci3 = 1 + ci; + if (ci3 != r.end()) { + assert(*ci3 == expected_range[1]); + } + } + + { // Check operator-(Iter, Diff) + same_as auto ci2 = ranges::next(ci) - 1; + assert(*ci2 == expected_range[0]); + } + } + + if constexpr (CartesianIsSizedSentinel...>) { // Check differencing + _Signed_integer_like auto diff = ci - ci; + assert(diff == 0); + assert(ci - ranges::next(ci) == -1); + assert(ranges::next(ci) - ci == 1); + } + + STATIC_ASSERT(sized_sentinel_for + == CartesianIsSizedSentinel...>); + if constexpr (sized_sentinel_for) { // Check differencing with default_sentinel + const auto expected_size = ranges::ssize(expected_range); + const _Signed_integer_like auto diff1 = ci - default_sentinel; + assert(diff1 == -expected_size); + const _Signed_integer_like auto diff2 = default_sentinel - ci; + assert(diff2 == expected_size); + } + + { // Check iter_move + same_as, + range_rvalue_reference_t>...>> decltype(auto) rval = iter_move(as_const(ci)); + assert(rval == expected_range[0]); + static_assert(noexcept(iter_move(ci)) == is_iter_move_nothrow...>()); + } + + if constexpr ((indirectly_swappable> && ... + && indirectly_swappable>>) ) { + // Check iter_swap, other tests are defined in test_iter_swap function + static_assert(is_void_v); + static_assert(noexcept(iter_swap(ci, ci)) == is_iter_swap_nothrow...>()); + } + } + + return true; +} + +// Check calling views::cartesian_product without arguments +STATIC_ASSERT(same_as); + +template + requires (indirectly_swappable> && ...) +constexpr void test_iter_swap(Rngs&... rngs) { + // This test assumes that 'ranges::size(rng)' is at least 2 for each rng in rngs + auto r = views::cartesian_product(rngs...); + using R = decltype(r); + using Val = ranges::range_value_t; + + { // Check iter_swap for cartesian_product_view::iterator + auto i = r.begin(); + Val first = *i; + auto j = ranges::next(i); + Val second = *j; + + iter_swap(i, j); + assert(*i == second); + assert(*j == first); + + ranges::iter_swap(i, j); + assert(*i == first); + assert(*j == second); + + static_assert(noexcept(iter_swap(i, j)) == is_iter_swap_nothrow()); + } + + // Check iter_swap for cartesian_product_view::iterator + if constexpr (((CanMemberBegin && indirectly_swappable>) &&...)) { + using CVal = ranges::range_value_t; + auto i = as_const(r).begin(); + CVal first = *i; + auto j = ranges::next(i); + CVal second = *j; + + iter_swap(i, j); + assert(*i == second); + assert(*j == first); + + ranges::iter_swap(i, j); + assert(*i == first); + assert(*j == second); + + static_assert(noexcept(iter_swap(i, j)) == is_iter_swap_nothrow()); + } +} + +constexpr tuple some_ranges = { + array{0, 1, 2, 3, 4}, + array{11, 22, 33}, + array{'7'}, + array{"4"sv, "2"sv, "0"sv}, +}; + +// Expected result of views::cartesian_product(get<0>(some_ranges)) +constexpr array, 5> expected_result_0{{{0}, {1}, {2}, {3}, {4}}}; + +// Expected result of views::cartesian_product(get<0>(some_ranges), get<1>(some_ranges)) +constexpr array, 15> expected_result_1{{{0, 11}, {0, 22}, {0, 33}, {1, 11}, {1, 22}, {1, 33}, {2, 11}, + {2, 22}, {2, 33}, {3, 11}, {3, 22}, {3, 33}, {4, 11}, {4, 22}, {4, 33}}}; + +// Expected result of views::cartesian_product(get<0>(some_ranges), ..., get<2>(some_ranges)) +constexpr array, 15> expected_result_2{ + {{0, 11, '7'}, {0, 22, '7'}, {0, 33, '7'}, {1, 11, '7'}, {1, 22, '7'}, {1, 33, '7'}, {2, 11, '7'}, {2, 22, '7'}, + {2, 33, '7'}, {3, 11, '7'}, {3, 22, '7'}, {3, 33, '7'}, {4, 11, '7'}, {4, 22, '7'}, {4, 33, '7'}}}; + +// Expected result of views::cartesian_product(get<0>(some_ranges), ..., get<3>(some_ranges)) +constexpr array, 45> expected_result_3{ + {{0, 11, '7', "4"sv}, {0, 11, '7', "2"sv}, {0, 11, '7', "0"sv}, {0, 22, '7', "4"sv}, {0, 22, '7', "2"sv}, + {0, 22, '7', "0"sv}, {0, 33, '7', "4"sv}, {0, 33, '7', "2"sv}, {0, 33, '7', "0"sv}, {1, 11, '7', "4"sv}, + {1, 11, '7', "2"sv}, {1, 11, '7', "0"sv}, {1, 22, '7', "4"sv}, {1, 22, '7', "2"sv}, {1, 22, '7', "0"sv}, + {1, 33, '7', "4"sv}, {1, 33, '7', "2"sv}, {1, 33, '7', "0"sv}, {2, 11, '7', "4"sv}, {2, 11, '7', "2"sv}, + {2, 11, '7', "0"sv}, {2, 22, '7', "4"sv}, {2, 22, '7', "2"sv}, {2, 22, '7', "0"sv}, {2, 33, '7', "4"sv}, + {2, 33, '7', "2"sv}, {2, 33, '7', "0"sv}, {3, 11, '7', "4"sv}, {3, 11, '7', "2"sv}, {3, 11, '7', "0"sv}, + {3, 22, '7', "4"sv}, {3, 22, '7', "2"sv}, {3, 22, '7', "0"sv}, {3, 33, '7', "4"sv}, {3, 33, '7', "2"sv}, + {3, 33, '7', "0"sv}, {4, 11, '7', "4"sv}, {4, 11, '7', "2"sv}, {4, 11, '7', "0"sv}, {4, 22, '7', "4"sv}, + {4, 22, '7', "2"sv}, {4, 22, '7', "0"sv}, {4, 33, '7', "4"sv}, {4, 33, '7', "2"sv}, {4, 33, '7', "0"sv}}}; + +template +struct test_input_range { + template + using type = test::range; +}; + +template +struct test_range { + template + using type = + test::range}, + IsCommon, test::CanCompare{derived_from || IsCommon == test::Common::yes}, + test::ProxyRef{!derived_from}>; +}; + +struct instantiator { + template + static constexpr void call() { + typename R::template type r0{get<0>(some_ranges)}; + test_one(expected_result_0, r0); + + if constexpr (ranges::forward_range>) { + typename R::template type r1{get<1>(some_ranges)}; + test_one(expected_result_1, r0, r1); + +#if !(defined(__clang__) && defined(_DEBUG) && !defined(_DLL)) // constexpr limit + typename R::template type r2{get<2>(some_ranges)}; + test_one(expected_result_2, r0, r1, r2); +#endif // "Clang /MTd" configuration + + int swap_a1[] = {1, 2, 3}; + typename R::template type swap_r1{swap_a1}; + int swap_a2[] = {9, 8, 7}; + typename R::template type swap_r2{swap_a2}; + test_iter_swap(swap_r1, swap_r2); + } + } +}; + +constexpr void instantiation_test() { + // The cartesian_product_view is sensitive to category, commonality, and size, but oblivious to + // differencing and proxyness. + using test::Common, test::Sized, test::CanDifference; + + // When the base range is an input range, the view is sensitive to differencing + instantiator::call>(); + instantiator::call>(); + + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); + instantiator::call>(); +} + +template > +using move_only_view = test::range}, + test::ProxyRef{!derived_from}, test::CanView::yes, test::Copyability::move_only>; + +namespace check_recommended_practice_implementation { // MSVC STL specific behavior + using ranges::cartesian_product_view, ranges::empty_view, ranges::single_view, views::all_t, ranges::range_size_t, + ranges::range_difference_t, ranges::ref_view, ranges::owning_view; + using Arr = array; + using Vec = vector; + using Span = span; + + // Computing product for such small array does not require big range_size_t + STATIC_ASSERT(sizeof(range_size_t>>) <= sizeof(size_t)); + STATIC_ASSERT(sizeof(range_size_t, all_t>>) <= sizeof(size_t)); + STATIC_ASSERT(sizeof(range_size_t, all_t, all_t>>) <= sizeof(size_t)); + + // Same thing with range_difference_t + STATIC_ASSERT(sizeof(range_difference_t>>) <= sizeof(ptrdiff_t)); + STATIC_ASSERT(sizeof(range_difference_t, all_t>>) <= sizeof(ptrdiff_t)); + STATIC_ASSERT( + sizeof(range_difference_t, all_t, all_t>>) <= sizeof(ptrdiff_t)); + + // Computing product for such small span does not require big range_size_t + STATIC_ASSERT(sizeof(range_size_t>>) <= sizeof(size_t)); + STATIC_ASSERT(sizeof(range_size_t, all_t>>) <= sizeof(size_t)); + STATIC_ASSERT( + sizeof(range_size_t, all_t, all_t>>) <= sizeof(size_t)); + + // Same thing with range_difference_t + STATIC_ASSERT(sizeof(range_difference_t>>) <= sizeof(ptrdiff_t)); + STATIC_ASSERT(sizeof(range_difference_t, all_t>>) <= sizeof(ptrdiff_t)); + STATIC_ASSERT( + sizeof(range_difference_t, all_t, all_t>>) <= sizeof(ptrdiff_t)); + + // Check 'single_view' and 'empty_view' + STATIC_ASSERT(sizeof(range_size_t, single_view>>) <= sizeof(size_t)); + STATIC_ASSERT( + sizeof(range_difference_t, single_view>>) <= sizeof(ptrdiff_t)); + + // Check 'ref_view<(const) V>' and 'owning_view' + STATIC_ASSERT(sizeof(range_size_t, ref_view, owning_view>>) + <= sizeof(size_t)); + STATIC_ASSERT( + sizeof(range_difference_t, ref_view, owning_view>>) + <= sizeof(ptrdiff_t)); + + // One vector should not use big integer-class type... + STATIC_ASSERT(sizeof(range_size_t>>) <= sizeof(size_t)); + STATIC_ASSERT(sizeof(range_difference_t>>) <= sizeof(ptrdiff_t)); + + // ... but two vectors will + STATIC_ASSERT(sizeof(range_size_t, all_t>>) > sizeof(size_t)); + STATIC_ASSERT(sizeof(range_difference_t, all_t>>) > sizeof(ptrdiff_t)); +} // namespace check_recommended_practice_implementation + +int main() { + // Check views + { // ... copyable + constexpr span s{get<0>(some_ranges)}; + STATIC_ASSERT(test_one(expected_result_0, s)); + test_one(expected_result_0, s); + } + + { // ... modifiable elements (so iterators are indirectly swappable) + auto arr = get<0>(some_ranges); + span s{arr}; + test_one(expected_result_0, s); + } + + { // ... move-only + using test::Common, test::Sized; + test_one(expected_result_3, // + move_only_view{get<0>(some_ranges)}, + move_only_view{get<1>(some_ranges)}, + move_only_view{get<2>(some_ranges)}, + move_only_view{get<3>(some_ranges)}); + test_one(expected_result_3, // + move_only_view{get<0>(some_ranges)}, + move_only_view{get<1>(some_ranges)}, + move_only_view{get<2>(some_ranges)}, + move_only_view{get<3>(some_ranges)}); + test_one(expected_result_2, // + move_only_view{get<0>(some_ranges)}, + move_only_view{get<1>(some_ranges)}, + move_only_view{get<2>(some_ranges)}); + test_one(expected_result_2, // + move_only_view{get<0>(some_ranges)}, + move_only_view{get<1>(some_ranges)}, + move_only_view{get<2>(some_ranges)}); + } + + // Check non-views + { + constexpr auto& r0 = get<0>(some_ranges); + STATIC_ASSERT(test_one(expected_result_0, r0)); + test_one(expected_result_0, r0); + + auto r1 = get<1>(some_ranges) | ranges::to(); + test_one(expected_result_1, r0, r1); + + auto r2 = get<2>(some_ranges) | ranges::to(); + test_one(expected_result_2, r0, r1, r2); + + auto r3 = get<3>(some_ranges) | ranges::to(); + test_one(expected_result_3, r0, r1, r2, r3); + } + +#ifndef _PREFAST_ // TRANSITION, GH-1030 + STATIC_ASSERT((instantiation_test(), true)); +#endif // TRANSITION, GH-1030 + instantiation_test(); +} diff --git a/tests/std/tests/P2374R4_views_cartesian_product_death/env.lst b/tests/std/tests/P2374R4_views_cartesian_product_death/env.lst new file mode 100644 index 0000000000..8ac7033b20 --- /dev/null +++ b/tests/std/tests/P2374R4_views_cartesian_product_death/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\strict_concepts_latest_matrix.lst diff --git a/tests/std/tests/P2374R4_views_cartesian_product_death/test.cpp b/tests/std/tests/P2374R4_views_cartesian_product_death/test.cpp new file mode 100644 index 0000000000..65b850caaf --- /dev/null +++ b/tests/std/tests/P2374R4_views_cartesian_product_death/test.cpp @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define _CONTAINER_DEBUG_LEVEL 1 + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +constexpr auto much_ints = views::iota(0ull, (numeric_limits::max)()); +constexpr array little_ints = {1, 2, 3, 4}; +void test_view_size() { + auto v = views::cartesian_product(much_ints, much_ints, much_ints); + // Size of cartesian product cannot be represented by _Size_type (N4928 [range.cartesian.view]/10). + (void) v.size(); +} + +void test_view_const_size() { + auto v = views::cartesian_product(much_ints, much_ints, much_ints); + // Size of cartesian product cannot be represented by _Size_type (N4928 [range.cartesian.view]/10). + (void) as_const(v).size(); +} + +void test_iterator_advance_past_end_with_small_offset() { + // This preconditions check works only when all ranges model ranges::sized_range + auto v = views::cartesian_product(little_ints, little_ints, little_ints); + auto i = v.begin(); + // Cannot advance cartesian_product_view iterator past end (N4928 [range.cartesian.iterator]/19). + i += 65; +} + +void test_iterator_advance_past_end_with_big_offset() { + // This preconditions check works only when all ranges model ranges::sized_range + auto v = views::cartesian_product(little_ints, little_ints, little_ints); + auto i = v.begin(); + // Cannot advance cartesian_product_view iterator past end (N4928 [range.cartesian.iterator]/19). + i += 1000; +} + +void test_iterator_advance_before_begin() { + auto v = views::cartesian_product(little_ints, little_ints, little_ints); + auto i = v.end(); + // Cannot advance cartesian_product_view iterator before begin (N4928 [range.cartesian.iterator]/19). + i += -65; +} + +void test_iterator_differencing() { + auto v = views::cartesian_product(much_ints, much_ints, much_ints); + auto i1 = v.begin(); + auto i2 = v.end(); + // Scaled-sum cannot be represented by _Difference_type (N4928 [range.cartesian.iterator]/8). + (void) (i2 - i1); +} + +void test_iterator_and_default_sentinel_differencing() { + auto v = views::cartesian_product(much_ints, much_ints, much_ints); + auto i = v.begin(); + // Scaled-sum cannot be represented by _Difference_type (N4928 [range.cartesian.iterator]/8). + (void) (default_sentinel - i); +} + +int main(int argc, char* argv[]) { + std_testing::death_test_executive exec; + +#if _ITERATOR_DEBUG_LEVEL != 0 + exec.add_death_tests({ + test_view_size, + test_view_const_size, + test_iterator_advance_past_end_with_small_offset, + test_iterator_advance_past_end_with_big_offset, + test_iterator_advance_before_begin, + test_iterator_differencing, + test_iterator_and_default_sentinel_differencing, + }); +#else // ^^^ test everything / test only _CONTAINER_DEBUG_LEVEL cases vvv + exec.add_death_tests({ + test_view_size, + test_view_const_size, + }); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + + return exec.run(argc, argv); +} diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp index c3e7c998c0..276dd6fdd8 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp @@ -1594,6 +1594,20 @@ STATIC_ASSERT(__cpp_lib_ranges_as_rvalue == 202207L); #endif #endif +#if _HAS_CXX23 && defined(__cpp_lib_concepts) // TRANSITION, GH-395 +#ifndef __cpp_lib_ranges_cartesian_product +#error __cpp_lib_ranges_cartesian_product is not defined +#elif __cpp_lib_ranges_cartesian_product != 202207L +#error __cpp_lib_ranges_cartesian_product is not 202207L +#else +STATIC_ASSERT(__cpp_lib_ranges_cartesian_product == 202207L); +#endif +#else +#ifdef __cpp_lib_ranges_cartesian_product +#error __cpp_lib_ranges_cartesian_product is defined +#endif +#endif + #if _HAS_CXX23 && defined(__cpp_lib_concepts) // TRANSITION, GH-395 #ifndef __cpp_lib_ranges_chunk #error __cpp_lib_ranges_chunk is not defined