From 1b5a0d6efd577f0df44b4032e0486444baa6f9f9 Mon Sep 17 00:00:00 2001 From: statementreply Date: Thu, 21 Jul 2022 08:31:30 +0800 Subject: [PATCH] Fix incorrect result of `complex` `log`/`log10`/`pow` on ARM64 (#2870) Co-authored-by: Stephan T. Lavavej --- stl/inc/complex | 42 +- stl/inc/ranges | 350 ++++++++++ stl/inc/yvals_core.h | 2 + tests/std/test.lst | 2 + .../log_test_cases.hpp | 4 + tests/std/tests/P1899R3_views_stride/env.lst | 4 + tests/std/tests/P1899R3_views_stride/test.cpp | 608 ++++++++++++++++++ .../tests/P1899R3_views_stride_death/env.lst | 4 + .../tests/P1899R3_views_stride_death/test.cpp | 73 +++ .../test.compile.pass.cpp | 14 + 10 files changed, 1079 insertions(+), 24 deletions(-) create mode 100644 tests/std/tests/P1899R3_views_stride/env.lst create mode 100644 tests/std/tests/P1899R3_views_stride/test.cpp create mode 100644 tests/std/tests/P1899R3_views_stride_death/env.lst create mode 100644 tests/std/tests/P1899R3_views_stride_death/test.cpp diff --git a/stl/inc/complex b/stl/inc/complex index 3faff546aec..2471b692de3 100644 --- a/stl/inc/complex +++ b/stl/inc/complex @@ -18,18 +18,19 @@ #ifdef _M_CEE_PURE // no intrinsics for /clr:pure -#elif defined(__clang__) -// TRANSITION, not using FMA intrinsics for Clang yet -#elif defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC)) +#elif defined(_M_ARM64) || defined(_M_ARM64EC) +// https://docs.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170#base-requirements +// Both floating-point and NEON support are presumed to be present in hardware. +#define _FMP_USING_STD_FMA +#elif defined(__clang__) // ^^^ defined(_M_ARM64) || defined(_M_ARM64EC) ^^^ +// TRANSITION, not using x86/x64 FMA intrinsics for Clang yet +#elif defined(_M_IX86) || defined(_M_X64) #define _FMP_USING_X86_X64_INTRINSICS #include #include extern "C" int __isa_available; extern "C" __m128d __cdecl _mm_fmsub_sd(__m128d, __m128d, __m128d); -#elif defined(_M_ARM64) || defined(_M_ARM64EC) -#define _FMP_USING_ARM64_INTRINSICS -#include -#endif // ^^^ defined(_M_ARM64) || defined(_M_ARM64EC) ^^^ +#endif // ^^^ defined(_M_IX86) || defined(_M_X64) ^^^ #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) @@ -78,7 +79,7 @@ namespace _Float_multi_prec { // 1x precision + 1x precision -> 2x precision // the result is exact when: - // 1) the result doesn't overflow + // 1) no internal overflow occurs // 2) either underflow is gradual, or no internal underflow occurs // 3) intermediate precision is either the same as _Ty, or greater than twice the precision of _Ty // 4) parameters and local variables do not retain extra intermediate precision @@ -99,7 +100,7 @@ namespace _Float_multi_prec { // requires: exponent(_Xval) + countr_zero(significand(_Xval)) >= exponent(_Yval) || _Xval == 0 // the result is exact when: // 0) the requirement above is satisfied - // 1) no internal overflow occurs + // 1) the result doesn't overflow // 2) either underflow is gradual, or no internal underflow occurs // 3) intermediate precision is either the same as _Ty, or greater than twice the precision of _Ty // 4) parameters and local variables do not retain extra intermediate precision @@ -160,16 +161,11 @@ namespace _Float_multi_prec { } #endif // _FMP_USING_X86_X64_INTRINSICS -#ifdef _FMP_USING_ARM64_INTRINSICS - _NODISCARD inline double _Sqr_error_arm64_neon(const double _Xval, const double _Prod0) noexcept { - const float64x1_t _Mx = vld1_f64(&_Xval); - const float64x1_t _Mprod0 = vld1_f64(&_Prod0); - const float64x1_t _Mresult = vfma_f64(vneg_f64(_Mprod0), _Mx, _Mx); - double _Result; - vst1_f64(&_Result, _Mresult); - return _Result; +#ifdef _FMP_USING_STD_FMA + _NODISCARD inline double _Sqr_error_std_fma(const double _Xval, const double _Prod0) noexcept { + return _STD fma(_Xval, _Xval, -_Prod0); } -#endif // _FMP_USING_ARM64_INTRINSICS +#endif // _FMP_USING_STD_FMA // square(1x precision) -> 2x precision // the result is exact when no internal overflow or underflow occurs @@ -189,11 +185,9 @@ namespace _Float_multi_prec { } #endif // ^^^ !defined(__AVX2__) ^^^ -#elif defined(_FMP_USING_ARM64_INTRINSICS) - // https://docs.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=vs-2019#base-requirements - // Both floating-point and NEON support are presumed to be present in hardware. - return {_Prod0, _Sqr_error_arm64_neon(_Xval, _Prod0)}; -#else // ^^^ defined(_FMP_USING_ARM64_INTRINSICS) / not using intrinsics vvv +#elif defined(_FMP_USING_STD_FMA) + return {_Prod0, _Sqr_error_std_fma(_Xval, _Prod0)}; +#else // ^^^ defined(_FMP_USING_STD_FMA) / not using intrinsics vvv return {_Prod0, _Sqr_error_fallback(_Xval, _Prod0)}; #endif // ^^^ not using intrinsics ^^^ } @@ -201,7 +195,7 @@ namespace _Float_multi_prec { #pragma float_control(pop) #undef _FMP_USING_X86_X64_INTRINSICS -#undef _FMP_USING_ARM64_INTRINSICS +#undef _FMP_USING_STD_FMA #define _FMP _STD _Float_multi_prec:: diff --git a/stl/inc/ranges b/stl/inc/ranges index 88488143a5d..26a64243c33 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -6352,6 +6352,356 @@ namespace ranges { inline constexpr _Chunk_by_fn chunk_by; } // namespace views + + template + requires view<_Vw> + class stride_view : public view_interface> { + private: + /* [[no_unique_address]] */ _Vw _Range; + range_difference_t<_Vw> _Stride; + + template + class _Iterator_base {}; + + template + class _Iterator_base<_BaseTy> { + private: + using _BaseCategory = typename iterator_traits>::iterator_category; + + public: + using iterator_category = conditional_t, + random_access_iterator_tag, _BaseCategory>; + }; + + template + class _Iterator : public _Iterator_base<_Maybe_const<_Const, _Vw>> { + private: + friend stride_view; + + using _ParentTy = _Maybe_const<_Const, stride_view>; + using _Base = _Maybe_const<_Const, _Vw>; + using _Base_iterator = iterator_t<_Base>; + using _Base_sentinel = sentinel_t<_Base>; + + /* [[no_unique_address]] */ _Base_iterator _Current{}; + /* [[no_unique_address]] */ _Base_sentinel _End{}; + range_difference_t<_Base> _Stride = 0; + range_difference_t<_Base> _Missing = 0; + + constexpr _Iterator(_ParentTy* _Parent, _Base_iterator _Current_, + range_difference_t<_Base> _Missing_ = 0) // + noexcept(noexcept(_RANGES end(_Parent->_Range)) + && is_nothrow_move_constructible_v< + _Base_iterator> && is_nothrow_move_constructible_v<_Base_sentinel>) // strengthened + : _Current(_STD move(_Current_)), _End(_RANGES end(_Parent->_Range)), _Stride(_Parent->_Stride), + _Missing(_Missing_) {} + + public: + using difference_type = range_difference_t<_Base>; + using value_type = range_value_t<_Base>; + using iterator_concept = conditional_t, random_access_iterator_tag, + conditional_t, bidirectional_iterator_tag, + conditional_t, forward_iterator_tag, input_iterator_tag>>>; + + // clang-format off + _Iterator() requires default_initializable<_Base_iterator> = default; + // clang-format on + + constexpr _Iterator(_Iterator _Other) noexcept( + is_nothrow_constructible_v<_Base_iterator, typename _Iterator::_Base_iterator> // + && is_nothrow_constructible_v<_Base_sentinel, typename _Iterator::_Base_sentinel> // + ) /* strengthened */ requires _Const + && convertible_to, _Base_iterator> && convertible_to, _Base_sentinel> + : _Current(_STD move(_Other._Current)), + _End(_STD move(_Other._End)), + _Stride(_Other._Stride), + _Missing(_Other._Missing) {} + + _NODISCARD constexpr _Base_iterator base() && noexcept( + is_nothrow_move_constructible_v<_Base_iterator>) /* strengthened */ { + return _STD move(_Current); + } + + _NODISCARD constexpr const _Base_iterator& base() const& noexcept { + return _Current; + } + + _NODISCARD constexpr decltype(auto) operator*() const noexcept(noexcept(*_Current)) /* strengthened */ { +#if _ITERATOR_DEBUG_LEVEL != 0 + _STL_VERIFY(_Current != _End, "cannot dereference stride_view end iterator"); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + return *_Current; + } + + constexpr _Iterator& operator++() { +#if _ITERATOR_DEBUG_LEVEL != 0 + _STL_VERIFY(_Current != _End, "cannot increment stride_view end iterator"); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + _Missing = ranges::advance(_Current, _Stride, _End); + return *this; + } + + constexpr void operator++(int) { + ++*this; + } + + constexpr _Iterator operator++(int) requires forward_range<_Base> { + auto _Tmp = *this; + ++*this; + return _Tmp; + } + + constexpr _Iterator& operator--() requires bidirectional_range<_Base> { + _RANGES advance(_Current, _Missing - _Stride); + _Missing = 0; + return *this; + } + + constexpr _Iterator operator--(int) requires bidirectional_range<_Base> { + auto _Tmp = *this; + --*this; + return _Tmp; + } + + constexpr _Iterator& operator+=(const difference_type _Off) requires random_access_range<_Base> { + if (_Off > 0) { +#if _ITERATOR_DEBUG_LEVEL != 0 + _STL_VERIFY(_Off == 1 || _Stride <= (numeric_limits::max)() / (_Off - 1), + "cannot advance stride_view iterator past end (integer overflow)"); + _STL_VERIFY(_RANGES distance(_Current, _End) > _Stride * (_Off - 1), + "cannot advance stride_view iterator past end"); +#endif // _ITERATOR_DEBUG_LEVEL != 0 + _Missing = _RANGES advance(_Current, _Stride * _Off, _End); + } else if (_Off < 0) { + _RANGES advance(_Current, _Stride * _Off + _Missing); + _Missing = 0; + } + return *this; + } + + constexpr _Iterator& operator-=(const difference_type _Off) requires random_access_range<_Base> { + return *this += -_Off; + } + + _NODISCARD constexpr decltype(auto) operator[](const difference_type _Off) const + noexcept(noexcept(*(*this + _Off))) /* strengthened */ requires random_access_range<_Base> { + return *(*this + _Off); + } + + _NODISCARD_FRIEND constexpr bool operator==(const _Iterator& _It, default_sentinel_t) noexcept( + noexcept(_Implicitly_convert_to(_It._Current == _It._End))) /* strengthened */ { + return _It._Current == _It._End; + } + + _NODISCARD_FRIEND constexpr bool operator==(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Implicitly_convert_to(_Left._Current == _Right._Current))) // strengthened + requires equality_comparable<_Base_iterator> { + return _Left._Current == _Right._Current; + } + + _NODISCARD_FRIEND constexpr bool operator<(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Implicitly_convert_to(_Left._Current < _Right._Current))) // strengthened + requires random_access_range<_Base> { + return _Left._Current < _Right._Current; + } + + _NODISCARD_FRIEND constexpr bool operator>(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Implicitly_convert_to(_Right._Current < _Left._Current))) // strengthened + requires random_access_range<_Base> { + return _Right._Current < _Left._Current; + } + + _NODISCARD_FRIEND constexpr bool operator<=(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Implicitly_convert_to(!(_Right._Current < _Left._Current)))) // strengthened + requires random_access_range<_Base> { + return !(_Right._Current < _Left._Current); + } + + _NODISCARD_FRIEND constexpr bool operator>=(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Implicitly_convert_to(!(_Left._Current < _Right._Current)))) // strengthened + requires random_access_range<_Base> { + return !(_Left._Current < _Right._Current); + } + + _NODISCARD_FRIEND constexpr auto operator<=>(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_Left._Current <=> _Right._Current)) // strengthened + requires random_access_range<_Base> && three_way_comparable<_Base_iterator> { + return _Left._Current <=> _Right._Current; + } + + + _NODISCARD_FRIEND constexpr _Iterator operator+(const _Iterator& _It, const difference_type _Off) // + requires random_access_range<_Base> { + auto _Copy = _It; + _Copy += _Off; + return _Copy; + } + + + _NODISCARD_FRIEND constexpr _Iterator operator+(const difference_type _Off, const _Iterator& _It) // + requires random_access_range<_Base> { + auto _Copy = _It; + _Copy += _Off; + return _Copy; + } + + _NODISCARD_FRIEND constexpr _Iterator operator-(const _Iterator& _It, const difference_type _Off) // + requires random_access_range<_Base> { + auto _Copy = _It; + _Copy -= _Off; + return _Copy; + } + + _NODISCARD_FRIEND constexpr difference_type operator-(const _Iterator& _Left, const _Iterator& _Right) // + noexcept(noexcept(_Left._Current - _Right._Current)) // strengthened + requires sized_sentinel_for<_Base_iterator, _Base_iterator> { + const auto _Diff = _Left._Current - _Right._Current; + if constexpr (forward_range<_Base>) { + return (_Diff + _Left._Missing - _Right._Missing) / _Left._Stride; + } else { + if (_Diff < 0) { + return -_Div_ceil(-_Diff, _Left._Stride); + } else { + return _Div_ceil(_Diff, _Left._Stride); + } + } + } + + _NODISCARD_FRIEND constexpr difference_type operator-(default_sentinel_t, const _Iterator& _It) noexcept( + noexcept(_It._End - _It._Current)) // strengthened + requires sized_sentinel_for<_Base_sentinel, _Base_iterator> { + return _Div_ceil(_It._End - _It._Current, _It._Stride); + } + + _NODISCARD_FRIEND constexpr difference_type operator-(const _Iterator& _It, default_sentinel_t) noexcept( + noexcept(_It._End - _It._Current)) // strengthened + requires sized_sentinel_for<_Base_sentinel, _Base_iterator> { + return -_Div_ceil(_It._End - _It._Current, _It._Stride); + } + + _NODISCARD_FRIEND constexpr range_rvalue_reference_t<_Base> iter_move(const _Iterator& _It) noexcept( + noexcept(_RANGES iter_move(_It._Current))) { + return _RANGES iter_move(_It._Current); + } + + friend constexpr void iter_swap(const _Iterator& _Left, const _Iterator& _Right) noexcept( + noexcept(_RANGES iter_swap(_Left._Current, _Right._Current))) // + requires indirectly_swappable<_Base_iterator> { + return _RANGES iter_swap(_Left._Current, _Right._Current); + } + }; + + public: + constexpr explicit stride_view(_Vw _Range_, range_difference_t<_Vw> _Stride_) noexcept( + is_nothrow_move_constructible_v<_Vw>) /* strengthened */ + : _Range(_STD move(_Range_)), _Stride(_Stride_) { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_Stride > 0, "stride must be greater than 0"); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + _NODISCARD constexpr _Vw base() const& noexcept(is_nothrow_copy_constructible_v<_Vw>) /* strengthened */ + requires copy_constructible<_Vw> { + return _Range; + } + + _NODISCARD constexpr _Vw base() && noexcept(is_nothrow_move_constructible_v<_Vw>) /* strengthened */ { + return _STD move(_Range); + } + + _NODISCARD constexpr range_difference_t<_Vw> stride() const noexcept { + return _Stride; + } + + // clang-format off + _NODISCARD constexpr auto begin() noexcept( + noexcept(_RANGES begin(_Range)) && is_nothrow_move_constructible_v>) // strengthened + requires (!_Simple_view<_Vw>) { + // clang-format on + return _Iterator{this, _RANGES begin(_Range)}; + } + + // clang-format off + _NODISCARD constexpr auto begin() const noexcept( + noexcept(_RANGES begin(_Range)) && is_nothrow_move_constructible_v>) // strengthened + requires range { + // clang-format on + return _Iterator{this, _RANGES begin(_Range)}; + } + + _NODISCARD constexpr auto end() requires(!_Simple_view<_Vw>) { + if constexpr (common_range<_Vw> && sized_range<_Vw> && forward_range<_Vw>) { + const auto _Missing = + static_cast>(_Stride - _RANGES distance(_Range) % _Stride); + if (_Missing == _Stride) { + return _Iterator{this, _RANGES end(_Range)}; + } else { + return _Iterator{this, _RANGES end(_Range), _Missing}; + } + } else if constexpr (common_range<_Vw> && !bidirectional_range<_Vw>) { + return _Iterator{this, _RANGES end(_Range)}; + } else { + return default_sentinel; + } + } + + _NODISCARD constexpr auto end() const requires range { + if constexpr (common_range && sized_range && forward_range) { + const auto _Missing = + static_cast>(_Stride - _RANGES distance(_Range) % _Stride); + if (_Missing == _Stride) { + return _Iterator{this, _RANGES end(_Range)}; + } else { + return _Iterator{this, _RANGES end(_Range), _Missing}; + } + } else if constexpr (common_range && !bidirectional_range) { + return _Iterator{this, _RANGES end(_Range)}; + } else { + return default_sentinel; + } + } + + _NODISCARD constexpr auto size() noexcept(noexcept(_RANGES distance(_Range))) // strengthened + requires sized_range<_Vw> { + return _To_unsigned_like(_Div_ceil(_RANGES distance(_Range), _Stride)); + } + + _NODISCARD constexpr auto size() const noexcept(noexcept(_RANGES distance(_Range))) // strengthened + requires sized_range { + return _To_unsigned_like(_Div_ceil(_RANGES distance(_Range), _Stride)); + } + }; + + template + stride_view(_Rng&&, range_difference_t<_Rng>) -> stride_view>; + + template + inline constexpr bool enable_borrowed_range> = enable_borrowed_range<_Vw>; + + namespace views { + struct _Stride_fn { + // clang-format off + template + _NODISCARD constexpr auto operator()(_Rng&& _Range, const range_difference_t<_Rng> _Stride) const noexcept( + noexcept(stride_view(_STD forward<_Rng>(_Range), _Stride))) requires requires { + stride_view(_STD forward<_Rng>(_Range), _Stride); + } { + // clang-format on + return stride_view(_STD forward<_Rng>(_Range), _Stride); + } + + // clang-format off + template + requires constructible_from, _Ty> + _NODISCARD constexpr auto operator()(_Ty&& _Stride) const + noexcept(is_nothrow_constructible_v, _Ty>) { + // clang-format on + return _Range_closure<_Stride_fn, decay_t<_Ty>>{_STD forward<_Ty>(_Stride)}; + } + }; + + inline constexpr _Stride_fn stride; + } // namespace views #endif // _HAS_CXX23 } // namespace ranges diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index b9064e29e88..64c4f203c81 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -302,6 +302,7 @@ // P1659R3 ranges::starts_with, ranges::ends_with // P1679R3 contains() For basic_string/basic_string_view // P1682R3 to_underlying() For Enumerations +// P1899R3 views::stride // P1951R1 Default Template Arguments For pair's Forwarding Constructor // P1989R2 Range Constructor For string_view // P2077R3 Heterogeneous Erasure Overloads For Associative Containers @@ -1466,6 +1467,7 @@ #define __cpp_lib_ranges_join_with 202202L #define __cpp_lib_ranges_slide 202202L #define __cpp_lib_ranges_starts_ends_with 202106L +#define __cpp_lib_ranges_stride 202207L #endif // __cpp_lib_concepts #define __cpp_lib_spanstream 202106L diff --git a/tests/std/test.lst b/tests/std/test.lst index 5121260b7f6..22425b075eb 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -481,6 +481,8 @@ tests\P1645R1_constexpr_numeric tests\P1659R3_ranges_alg_ends_with tests\P1659R3_ranges_alg_starts_with tests\P1682R3_to_underlying +tests\P1899R3_views_stride +tests\P1899R3_views_stride_death tests\P1951R1_default_arguments_pair_forward_ctor tests\P2136R3_invoke_r tests\P2162R2_std_visit_for_derived_classes_from_variant diff --git a/tests/std/tests/GH_000935_complex_numerical_accuracy/log_test_cases.hpp b/tests/std/tests/GH_000935_complex_numerical_accuracy/log_test_cases.hpp index d67e12dd1b5..7caf981eef9 100644 --- a/tests/std/tests/GH_000935_complex_numerical_accuracy/log_test_cases.hpp +++ b/tests/std/tests/GH_000935_complex_numerical_accuracy/log_test_cases.hpp @@ -32,6 +32,10 @@ constexpr complex_unary_test_case log_double_cases[] = { {{-0x1.8p-2, +0x1p-1}, {-0x1.e148a1a2726cep-2, +0x1.1b6e192ebbe44p+1}}, {{-0x1.8p-2, -0x1p-1}, {-0x1.e148a1a2726cep-2, -0x1.1b6e192ebbe44p+1}}, + // DevCom-10088405: Incorrect result for std::complex operations on ARM64 platform + {{0.1, 1.2}, {0.18578177821624148, 1.4876550949064553}}, + {{-1.1698230349239351, 0.46519593659281616}, {0.23025850929940467, 2.763102111592855}}, + // special cases {{+1.0, +0.0}, {0.0, +0.0}, {true, true}}, {{+1.0, -0.0}, {0.0, -0.0}, {true, true}}, diff --git a/tests/std/tests/P1899R3_views_stride/env.lst b/tests/std/tests/P1899R3_views_stride/env.lst new file mode 100644 index 00000000000..8ac7033b206 --- /dev/null +++ b/tests/std/tests/P1899R3_views_stride/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/P1899R3_views_stride/test.cpp b/tests/std/tests/P1899R3_views_stride/test.cpp new file mode 100644 index 00000000000..8731b81b4a1 --- /dev/null +++ b/tests/std/tests/P1899R3_views_stride/test.cpp @@ -0,0 +1,608 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +using namespace std; + +template +concept CanViewStride = requires(Rng&& r) { + views::stride(forward(r), 3); +}; + +template +constexpr bool test_one(Rng&& rng, Expected&& expected) { + using ranges::bidirectional_range, ranges::common_range, ranges::forward_range, ranges::random_access_range, + ranges::sized_range, ranges::range; + using ranges::stride_view, ranges::begin, ranges::end, ranges::equal, ranges::iterator_t, ranges::sentinel_t, + ranges::prev; + constexpr bool is_view = ranges::view>; + + using V = views::all_t; + using R = stride_view; + + STATIC_ASSERT(ranges::view); + STATIC_ASSERT(forward_range == forward_range); + STATIC_ASSERT(bidirectional_range == bidirectional_range); + STATIC_ASSERT(random_access_range == random_access_range); + + // Validate borrowed_range + STATIC_ASSERT(ranges::borrowed_range == ranges::borrowed_range); + + // Validate range adaptor object and range adaptor closure + constexpr auto closure = views::stride(3); + + // ... with lvalue argument + STATIC_ASSERT(CanViewStride == (!is_view || copy_constructible) ); + if constexpr (CanViewStride) { + constexpr bool is_noexcept = !is_view || is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(views::stride(rng, 3)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(rng | closure) == is_noexcept); + } + + // ... with const lvalue argument + STATIC_ASSERT(CanViewStride&> == (!is_view || copy_constructible) ); + if constexpr (CanViewStride&>) { + using RC = stride_view&>>; + constexpr bool is_noexcept = !is_view || is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(views::stride(as_const(rng), 3)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(as_const(rng) | closure) == is_noexcept); + } + + // ... with rvalue argument + STATIC_ASSERT(CanViewStride> == (is_view || movable>) ); + if constexpr (CanViewStride>) { + using RS = stride_view>>; + constexpr bool is_noexcept = is_nothrow_move_constructible_v; + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(views::stride(move(rng), 3)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(move(rng) | closure) == is_noexcept); + } + + // ... with const rvalue argument + STATIC_ASSERT(CanViewStride> == (is_view && copy_constructible) ); + if constexpr (CanViewStride>) { + constexpr bool is_noexcept = is_nothrow_copy_constructible_v; + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(views::stride(move(as_const(rng)), 3)) == is_noexcept); + + STATIC_ASSERT(same_as); + STATIC_ASSERT(noexcept(move(as_const(rng)) | closure) == is_noexcept); + } + + // Validate deduction guide + same_as auto r = stride_view{std::forward(rng), 3}; + const bool is_empty = ranges::empty(expected); + + // Validate stride_view::stride + { + const same_as> auto sc = as_const(r).stride(); + assert(sc == 3); + STATIC_ASSERT(noexcept(as_const(r).stride())); + } + + // Validate stride_view::size + STATIC_ASSERT(CanMemberSize == sized_range); + if constexpr (CanMemberSize) { + same_as<_Make_unsigned_like_t>> auto s = r.size(); + assert(s == ranges::size(expected)); + STATIC_ASSERT(noexcept(r.size())); // TODO sure? + } + + STATIC_ASSERT(CanMemberSize == sized_range); + if constexpr (CanMemberSize) { + same_as<_Make_unsigned_like_t>> auto s = as_const(r).size(); + assert(s == ranges::size(expected)); + STATIC_ASSERT(noexcept(as_const(r).size())); // TODO sure? + } + + // Validate view_interface::empty and operator bool + STATIC_ASSERT(CanMemberEmpty == (forward_range /*|| sized_range*/)); + STATIC_ASSERT(CanBool == CanEmpty); + if constexpr (CanMemberEmpty) { + assert(r.empty() == is_empty); + assert(static_cast(r) == !is_empty); + } + + STATIC_ASSERT(CanMemberEmpty == (forward_range /*|| sized_range*/)); + STATIC_ASSERT(CanBool == CanEmpty); + if constexpr (CanMemberEmpty) { + assert(as_const(r).empty() == is_empty); + assert(static_cast(as_const(r)) == !is_empty); + } + + // Validate content + assert(ranges::equal(r, expected)); + if (!forward_range) { // intentionally not if constexpr [TODO] + return true; + } + + // Validate stride_view::begin + STATIC_ASSERT(CanMemberBegin); + { + const same_as> auto i = r.begin(); + if (!is_empty) { + assert(*i == *begin(expected)); + } + + if constexpr (copy_constructible) { + auto r2 = r; + const same_as> auto i2 = r2.begin(); + if (!is_empty) { + assert(*i2 == *i); + } + } + } + + // Validate stride_view::begin (const) + STATIC_ASSERT(CanMemberBegin == range); + if constexpr (CanMemberBegin) { + const same_as> auto ci = as_const(r).begin(); + if (!is_empty) { + assert(*ci == *begin(expected)); + } + + if constexpr (copy_constructible) { + const auto r2 = r; + const same_as> auto ci2 = r2.begin(); + if (!is_empty) { + assert(*ci2 == *ci); + } + } + } + + // Validate stride_view::end + STATIC_ASSERT(CanMemberEnd); + { + const same_as> auto s = r.end(); + assert((r.begin() == s) == is_empty); + STATIC_ASSERT(common_range == (common_range && (sized_range || !bidirectional_range) )); + if constexpr (common_range && bidirectional_range) { + if (!is_empty) { + assert(*prev(s) == *prev(end(expected))); + } + + if constexpr (copy_constructible) { + auto r2 = r; + if (!is_empty) { + assert(*prev(r2.end()) == *prev(end(expected))); + } + } + } + } + + // Validate stride_view::end (const) + STATIC_ASSERT(CanMemberEnd == range); + if constexpr (CanMemberEnd) { + const same_as> auto cs = as_const(r).end(); + assert((as_const(r).begin() == cs) == is_empty); + STATIC_ASSERT(common_range == // + (common_range && (sized_range || !bidirectional_range) )); + if constexpr (common_range && bidirectional_range) { + if (!is_empty) { + assert(*prev(cs) == *prev(end(expected))); + } + + if constexpr (copy_constructible) { + const auto r2 = r; + if (!is_empty) { + assert(*prev(r2.end()) == *prev(end(expected))); + } + } + } + } + + // Validate view_interface::data + STATIC_ASSERT(!CanData); + STATIC_ASSERT(!CanData); + + if (is_empty) { + return true; + } + + // Validate view_interface::operator[] + STATIC_ASSERT(CanIndex == random_access_range); + if constexpr (CanIndex) { + assert(r[0] == expected[0]); + } + + STATIC_ASSERT(CanIndex == random_access_range); + if constexpr (CanIndex) { + assert(as_const(r)[0] == expected[0]); + } + + // Validate view_interface::front + STATIC_ASSERT(CanMemberFront == forward_range); + if constexpr (CanMemberFront) { + assert(r.front() == *begin(expected)); + } + + // Validate view_interface::front (const) + STATIC_ASSERT(CanMemberFront == forward_range); + if constexpr (CanMemberFront) { + assert(as_const(r).front() == *begin(expected)); + } + + // Validate view_interface::back + STATIC_ASSERT(CanMemberBack == (bidirectional_range && common_range && sized_range) ); + if constexpr (CanMemberBack) { + assert(r.back() == *prev(end(expected))); + } + + // Validate view_interface::back (const) + STATIC_ASSERT( + CanMemberBack == (bidirectional_range && common_range && sized_range) ); + if constexpr (CanMemberBack) { + assert(as_const(r).back() == *prev(end(expected))); + } + + // Validate stride_view::_Iterator + { + if constexpr (forward_range) { + [[maybe_unused]] const iterator_t defaulted; + } + same_as> auto i = r.begin(); + + if constexpr (forward_range) { + assert(*i++ == expected[0]); + } else { + i++; + } + assert(*++i == expected[2]); + + if constexpr (bidirectional_range) { + assert(*i-- == expected[2]); + assert(*--i == expected[0]); + } + + if constexpr (random_access_range) { + i += 2; + assert(*i == expected[2]); + + i -= 2; + assert(*i == expected[0]); + + assert(i[2] == expected[2]); + + const same_as> auto i2 = i + 2; + assert(*i2 == expected[2]); + + const same_as> auto i3 = 2 + i; + assert(*i3 == expected[2]); + + const same_as> auto i4 = i3 - 2; + assert(*i4 == expected[0]); + + const same_as> auto diff1 = i2 - i; + assert(diff1 == 2); + + const same_as> auto diff2 = i - i2; + assert(diff2 == -2); + + // comparisons + assert(i == i4); + assert(i != i2); + + assert(i < i2); + assert(i <= i2); + assert(i2 > i); + assert(i2 >= i); + + if constexpr (three_way_comparable>) { + assert(i <=> i4 == strong_ordering::equal); + assert(i <=> i2 == strong_ordering::less); + assert(i2 <=> i == strong_ordering::greater); + } + } + + if constexpr (!common_range) { + if constexpr (forward_range) { + [[maybe_unused]] const sentinel_t sentinel_defaulted; + } + + const auto i2 = r.begin(); + const auto sen = r.end(); + const auto size = ranges::ssize(expected); + + if constexpr (forward_range) { + assert(next(r.begin(), size) == sen); + } + assert(i2 != sen); + + if constexpr (sized_sentinel_for, iterator_t>) { + const same_as> auto diff3 = i2 - sen; + assert(diff3 == -size); + + const same_as> auto diff4 = sen - i2; + assert(diff4 == size); + } + } + + // Validate sized sentinels + { + // const auto i2 = r.begin(); + // const auto i3 = r.begin(); + } + } + + // Validate stride_view::_Iterator + if constexpr (CanMemberBegin) { + if constexpr (forward_range) { + [[maybe_unused]] const iterator_t const_defaulted; + } + auto i{r.begin()}; + auto ci{as_const(r).begin()}; + + if constexpr (forward_range) { + assert(*ci++ == expected[0]); + } + assert(*++ci == expected[2]); + + if constexpr (bidirectional_range) { + assert(*ci-- == expected[2]); + assert(*--ci == expected[0]); + } + + if constexpr (random_access_range) { + ci += 2; + assert(*ci == expected[2]); + + ci -= 2; + assert(*ci == expected[0]); + + assert(ci[2] == expected[2]); + + const same_as> auto ci2 = ci + 2; + assert(*ci2 == expected[2]); + + const same_as> auto ci3 = 2 + ci; + assert(*ci3 == expected[2]); + + const same_as> auto ci4 = ci3 - 2; + assert(*ci4 == expected[0]); + + const same_as> auto diff1 = ci2 - ci; + assert(diff1 == 2); + + const same_as> auto diff2 = ci - ci2; + assert(diff2 == -2); + + // comparisons + assert(ci == ci4); + assert(ci != ci2); + + assert(ci < ci2); + assert(ci <= ci2); + assert(ci2 > ci); + assert(ci2 >= ci); + + // cross comparisons + assert(ci == i); + assert(ci2 != i); + + assert(i < ci2); + assert(i <= ci2); + assert(ci2 > i); + assert(ci2 >= i); + + if constexpr (three_way_comparable>) { + assert(ci <=> ci4 == strong_ordering::equal); + assert(ci <=> ci2 == strong_ordering::less); + assert(ci2 <=> ci == strong_ordering::greater); + } + } + + // TODO + // if constexpr (!common_range) { + // [[maybe_unused]] const sentinel_t sentinel_defaulted; + + // const auto i2 = as_const(r).begin(); + // const auto sen = r.end(); + // const auto size = ranges::ssize(expected); + + // assert(next(i2, size) == sen); + // assert(i2 != sen); + + // if constexpr (sized_sentinel_for, iterator_t>) { + // const same_as> auto diff3 = i2 - sen; + // assert(diff3 == -size); + + // const same_as> auto diff4 = sen - i2; + // assert(diff4 == size); + // } + // } + } + + // Validate stride_view::base() const& + STATIC_ASSERT(CanMemberBase == copy_constructible); + if constexpr (copy_constructible) { + same_as auto b1 = as_const(r).base(); + STATIC_ASSERT(noexcept(as_const(r).base()) == is_nothrow_copy_constructible_v); + assert(*b1.begin() == *begin(expected)); + } + + // Validate stride_view::base() && + same_as auto b2 = std::move(r).base(); + STATIC_ASSERT(noexcept(std::move(r).base()) == is_nothrow_move_constructible_v); + if (!is_empty) { + assert(*b2.begin() == *begin(expected)); + } + + return true; +} + +static constexpr int some_ints[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}; +static constexpr int expected_stride3[] = {0, 3, 6, 9, 12, 15}; + +// template +// constexpr bool test_input(Rng&& rng, Expected&& expected) { +// using ranges::chunk_view, ranges::equal, ranges::iterator_t, ranges::sentinel_t; + +// using V = views::all_t; +// using R = chunk_view; + +// same_as auto r = chunk_view{forward(rng), 2}; +// auto outer_iter = r.begin(); + +// auto val_ty = *outer_iter; +// if constexpr (sized_sentinel_for, iterator_t>) { +// assert(val_ty.size() == 2); +// } + +// auto inner_iter = val_ty.begin(); +// same_as auto inner_sen = val_ty.end(); +// assert(inner_iter != inner_sen); +// if constexpr (sized_sentinel_for, iterator_t>) { +// assert(inner_sen - inner_iter == 2); +// assert(inner_iter - inner_sen == -2); +// } + +// ++inner_iter; +// if constexpr (sized_sentinel_for, iterator_t>) { +// assert(inner_sen - inner_iter == 1); +// assert(inner_iter - inner_sen == -1); +// } + +// inner_iter++; +// if constexpr (sized_sentinel_for, iterator_t>) { +// assert(inner_sen - inner_iter == 0); +// assert(inner_iter - inner_sen == 0); +// } +// assert(inner_iter == inner_sen); + +// outer_iter++; +// assert(equal(*outer_iter, expected[1])); +// ++outer_iter; +// assert(equal(*outer_iter, expected[2])); +// ++outer_iter; + +// same_as auto outer_sen = r.end(); +// assert(outer_sen != outer_iter); + +// if constexpr (sized_sentinel_for, iterator_t>) { +// assert(outer_sen - outer_iter == 1); +// assert(outer_iter - outer_sen == -1); +// } + +// ++outer_iter; +// assert(outer_sen == outer_iter); + +// return true; +// } + +struct instantiator { + template + static constexpr void call() { + R r{some_ints}; + test_one(r, expected_stride3); + } +}; + +template +using test_input_range = test::range; + +template +using test_range = + test::range}, + IsCommon, test::CanCompare{derived_from || IsCommon == test::Common::yes}, + test::ProxyRef{!derived_from}>; + +constexpr void instantiation_test() { +#ifdef TEST_EVERYTHING + test_in(); +#else // ^^^ test all input range permutations / test only "interesting" permutations vvv + using test::CanDifference, test::Common, test::Sized; + + // When the base range is an input range, + // the view is sensitive to differencing. + instantiator::call>(); + instantiator::call>(); + + // When the base range is a forward range, + // the view is sensitive to category, commonality, and size, but oblivious to differencing and proxyness. + 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>(); +#endif // TEST_EVERYTHING +} + +template > +using move_only_view = test::range}, + test::ProxyRef{!derived_from}, test::CanView::yes, test::Copyability::move_only>; + +int main() { + // { // Validate views + // // ... copyable + // constexpr span s{some_ints}; + // STATIC_ASSERT(test_one(s, expected_stride3)); + // test_one(s, expected_stride3); + // } + + //{ // ... move-only + // test_one(move_only_view{some_ints}, expected_stride3); + // test_one(move_only_view{some_ints}, expected_stride3); + // test_one(move_only_view{some_ints}, expected_stride3); + // test_one(move_only_view{some_ints}, expected_stride3); + // test_one(move_only_view{some_ints}, expected_stride3); + // test_one(move_only_view{some_ints}, expected_stride3); + // test_one(move_only_view{some_ints}, expected_stride3); + //} + + // { // Validate non-views + // STATIC_ASSERT(test_one(some_ints, expected_stride3)); + // test_one(some_ints, expected_stride3); + // } + // { + // vector vec(ranges::begin(some_ints), ranges::end(some_ints)); + // test_one(vec, expected_stride3); + // } + // { + // forward_list lst(ranges::begin(some_ints), ranges::end(some_ints)); + // test_one(lst, expected_stride3); + // } + + // { // empty range + // STATIC_ASSERT(test_one(span{}, span{})); + // test_one(span{}, span{}); + // } + + STATIC_ASSERT((instantiation_test(), true)); + instantiation_test(); +} diff --git a/tests/std/tests/P1899R3_views_stride_death/env.lst b/tests/std/tests/P1899R3_views_stride_death/env.lst new file mode 100644 index 00000000000..8ac7033b206 --- /dev/null +++ b/tests/std/tests/P1899R3_views_stride_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/P1899R3_views_stride_death/test.cpp b/tests/std/tests/P1899R3_views_stride_death/test.cpp new file mode 100644 index 00000000000..8ba514158f1 --- /dev/null +++ b/tests/std/tests/P1899R3_views_stride_death/test.cpp @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define _CONTAINER_DEBUG_LEVEL 1 + +#include +#include +#include + +#include +#include + +using namespace std; +using ranges::stride_view; + +using test_input_range = test::range; + +static constexpr int some_ints[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + +void test_view_negative_stride() { + [[maybe_unused]] auto v = stride_view(some_ints, -1); // stride must be greater than 0 +} + +void test_iterator_dereference_at_end() { + auto v = stride_view(some_ints, 2); + auto it = v.end(); + (void) *it; // cannot dereference stride_view end iterator +} + +void test_iterator_preincrement_past_end() { + auto v = stride_view(some_ints, 2); + auto it = v.end(); + ++it; // cannot increment stride_view end iterator +} + +void test_input_iterator_postincrement_past_end() { + auto r = test_input_range{some_ints}; + auto v = stride_view(r, 2); + auto it = v.end(); + it++; // cannot increment stride_view end iterator +} + +void test_forward_iterator_postincrement_past_end() { + auto v = stride_view(some_ints, 2); + auto it = v.end(); + it++; // cannot increment stride_view end iterator +} + +void test_iterator_advance_past_end() { + auto v = stride_view(some_ints, 2); + auto it = v.begin(); + it += 100; // cannot advance stride_view iterator past end +} + +int main(int argc, char* argv[]) { + std_testing::death_test_executive exec; + +#if _ITERATOR_DEBUG_LEVEL != 0 + exec.add_death_tests({ + test_view_negative_stride, + test_iterator_dereference_at_end, + test_iterator_preincrement_past_end, + test_input_iterator_postincrement_past_end, + test_forward_iterator_postincrement_past_end, + test_iterator_advance_past_end, + }); +#else // ^^^ test everything / test only _CONTAINER_DEBUG_LEVEL cases vvv + exec.add_death_tests({test_view_negative_stride}); +#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 34ca2d2adb7..c852c4063e5 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 @@ -1464,6 +1464,20 @@ STATIC_ASSERT(__cpp_lib_ranges_starts_ends_with == 202106L); #endif #endif +#if _HAS_CXX23 && !defined(__EDG__) // TRANSITION, EDG concepts support +#ifndef __cpp_lib_ranges_stride +#error __cpp_lib_ranges_stride is not defined +#elif __cpp_lib_ranges_stride != 202207L +#error __cpp_lib_ranges_stride is not 202107L +#else +STATIC_ASSERT(__cpp_lib_ranges_stride == 202207L); +#endif +#else +#ifdef __cpp_lib_ranges_stride +#error __cpp_lib_ranges_stride is defined +#endif +#endif + #if _HAS_CXX17 #ifndef __cpp_lib_raw_memory_algorithms #error __cpp_lib_raw_memory_algorithms is not defined