diff --git a/benchmarks/src/includes.cpp b/benchmarks/src/includes.cpp index 7ea6ea9364b..2a54ff4a1da 100644 --- a/benchmarks/src/includes.cpp +++ b/benchmarks/src/includes.cpp @@ -111,6 +111,16 @@ void common_args(auto bm) { } } +BENCHMARK(bm_includes)->Apply(common_args); +BENCHMARK(bm_includes)->Apply(common_args); +BENCHMARK(bm_includes)->Apply(common_args); +BENCHMARK(bm_includes)->Apply(common_args); + +BENCHMARK(bm_includes)->Apply(common_args); +BENCHMARK(bm_includes)->Apply(common_args); +BENCHMARK(bm_includes)->Apply(common_args); +BENCHMARK(bm_includes)->Apply(common_args); + BENCHMARK(bm_includes)->Apply(common_args); BENCHMARK(bm_includes)->Apply(common_args); BENCHMARK(bm_includes)->Apply(common_args); diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 1ceda78079b..5a00b6fd6fa 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -86,6 +86,23 @@ const void* __stdcall __std_is_sorted_until_8u(const void* _First, const void* _ const void* __stdcall __std_is_sorted_until_f(const void* _First, const void* _Last, bool _Greater) noexcept; const void* __stdcall __std_is_sorted_until_d(const void* _First, const void* _Last, bool _Greater) noexcept; +__declspec(noalias) bool __stdcall __std_includes_less_1i( + const void* _First1, const void* _Last1, const void* _First2, const void* _Last2) noexcept; +__declspec(noalias) bool __stdcall __std_includes_less_1u( + const void* _First1, const void* _Last1, const void* _First2, const void* _Last2) noexcept; +__declspec(noalias) bool __stdcall __std_includes_less_2i( + const void* _First1, const void* _Last1, const void* _First2, const void* _Last2) noexcept; +__declspec(noalias) bool __stdcall __std_includes_less_2u( + const void* _First1, const void* _Last1, const void* _First2, const void* _Last2) noexcept; +__declspec(noalias) bool __stdcall __std_includes_less_4i( + const void* _First1, const void* _Last1, const void* _First2, const void* _Last2) noexcept; +__declspec(noalias) bool __stdcall __std_includes_less_4u( + const void* _First1, const void* _Last1, const void* _First2, const void* _Last2) noexcept; +__declspec(noalias) bool __stdcall __std_includes_less_8i( + const void* _First1, const void* _Last1, const void* _First2, const void* _Last2) noexcept; +__declspec(noalias) bool __stdcall __std_includes_less_8u( + const void* _First1, const void* _Last1, const void* _First2, const void* _Last2) noexcept; + // TRANSITION, DevCom-10610477 __declspec(noalias) void __stdcall __std_replace_4( void* _First, void* _Last, uint32_t _Old_val, uint32_t _New_val) noexcept; @@ -256,6 +273,40 @@ _Ty* _Is_sorted_until_vectorized(_Ty* const _First, _Ty* const _Last, const bool } } +template +bool _Includes_vectorized( + const _Ty* const _First1, const _Ty* const _Last1, const _Ty* const _First2, const _Ty* const _Last2) noexcept { + constexpr bool _Signed = is_signed_v<_Ty>; + + if constexpr (sizeof(_Ty) == 1) { + if constexpr (_Signed) { + return ::__std_includes_less_1i(_First1, _Last1, _First2, _Last2); + } else { + return ::__std_includes_less_1u(_First1, _Last1, _First2, _Last2); + } + } else if constexpr (sizeof(_Ty) == 2) { + if constexpr (_Signed) { + return ::__std_includes_less_2i(_First1, _Last1, _First2, _Last2); + } else { + return ::__std_includes_less_2u(_First1, _Last1, _First2, _Last2); + } + } else if constexpr (sizeof(_Ty) == 4) { + if constexpr (_Signed) { + return ::__std_includes_less_4i(_First1, _Last1, _First2, _Last2); + } else { + return ::__std_includes_less_4u(_First1, _Last1, _First2, _Last2); + } + } else if constexpr (sizeof(_Ty) == 8) { + if constexpr (_Signed) { + return ::__std_includes_less_8i(_First1, _Last1, _First2, _Last2); + } else { + return ::__std_includes_less_8u(_First1, _Last1, _First2, _Last2); + } + } else { + _STL_INTERNAL_STATIC_ASSERT(false); // unexpected size + } +} + template __declspec(noalias) void _Replace_vectorized( _Ty* const _First, _Ty* const _Last, const _TVal1 _Old_val, const _TVal2 _New_val) noexcept { @@ -384,6 +435,13 @@ constexpr bool _Output_iterator_for_vector_alg_is_safe() { } } +// Can we activate the vector algorithms for includes? +template > +constexpr bool _Vector_alg_includes_iterators_safe = + _Iterators_are_contiguous<_Iter1, _Iter2> // Iterators must be contiguous so we can get raw pointers. + && !_Iterator_is_volatile<_Iter1> && !_Iterator_is_volatile<_Iter2> // Iterators must not be volatile. + && is_same_v<_Elem, _Iter_value_t<_Iter2>> // Iterators have the same value type. + && disjunction_v, is_pointer<_Elem>>; // Integral or pointer type. _STD_END #endif // _USE_STD_VECTOR_ALGORITHMS @@ -10244,6 +10302,15 @@ _NODISCARD _CONSTEXPR20 bool includes(_InIt1 _First1, _InIt1 _Last1, _InIt2 _Fir return false; } +#if _USE_STD_VECTOR_ALGORITHMS + if constexpr (_Vector_alg_includes_iterators_safe<_InIt1, _InIt2> && _Is_predicate_less<_InIt1, _Pr>) { + if (!_STD _Is_constant_evaluated()) { + return _STD _Includes_vectorized(_STD _To_address(_First1), _STD _To_address(_Last1), + _STD _To_address(_First2), _STD _To_address(_Last2)); + } + } +#endif // _USE_STD_VECTOR_ALGORITHMS + for (;;) { if (_DEBUG_LT_PRED(_Pred, *_UFirst1, *_UFirst2)) { ++_UFirst1; @@ -10333,6 +10400,20 @@ namespace ranges { return false; } +#if _USE_STD_VECTOR_ALGORITHMS + if constexpr (_Vector_alg_includes_iterators_safe<_It1, _It2> && _Is_predicate_less<_It1, _Pr> + && sized_sentinel_for<_Se1, _It1> && sized_sentinel_for<_Se2, _It2> + && is_same_v<_Pj1, identity> && is_same_v<_Pj2, identity>) { + if (!_STD is_constant_evaluated()) { + const auto _First1_ptr = _STD to_address(_First1); + const auto _First2_ptr = _STD to_address(_First2); + const auto _Last1_ptr = _First1_ptr + static_cast(_Last1 - _First1); + const auto _Last2_ptr = _First2_ptr + static_cast(_Last2 - _First2); + return _STD _Includes_vectorized(_First1_ptr, _Last1_ptr, _First2_ptr, _Last2_ptr); + } + } +#endif // _USE_STD_VECTOR_ALGORITHMS + for (;;) { if (_STD invoke(_Pred, _STD invoke(_Proj1, *_First1), _STD invoke(_Proj2, *_First2))) { ++_First1; diff --git a/stl/src/vector_algorithms.cpp b/stl/src/vector_algorithms.cpp index ce3f0335c23..ea38e0e39d7 100644 --- a/stl/src/vector_algorithms.cpp +++ b/stl/src/vector_algorithms.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #ifndef _M_ARM64EC #include @@ -6890,6 +6890,390 @@ void* __stdcall __std_unique_copy_8(const void* _First, const void* const _Last, } // extern "C" +namespace { + namespace _Sorted_ranges { +#ifdef _M_ARM64EC + using _Traits_1_avx = void; + using _Traits_2_avx = void; + using _Traits_4_avx = void; + using _Traits_8_avx = void; + using _Traits_1_sse = void; + using _Traits_2_sse = void; + using _Traits_4_sse = void; + using _Traits_8_sse = void; +#else // ^^^ defined(_M_ARM64EC) / !defined(_M_ARM64EC) vvv + struct _Traits_avx { + using _Guard = _Zeroupper_on_exit; + static constexpr size_t _Vec_size = 32; + static constexpr size_t _Tail_mask = 0x1C; + + static __m256i _Load(const void* const _Src) noexcept { + return _mm256_loadu_si256(reinterpret_cast(_Src)); + } + + static __m256i _Load_mask(const void* const _Src, const __m256i _Mask) noexcept { + return _mm256_maskload_epi32(reinterpret_cast(_Src), _Mask); + } + + static unsigned long _Mask(const __m256i _Val) noexcept { + return _mm256_movemask_epi8(_Val); + } + + static uint32_t _Bsf(const uint32_t _Val) noexcept { + return _tzcnt_u32(_Val); + } + }; + + struct _Traits_1_avx : _Traits_avx { + static __m256i _Broadcast(const uint8_t _Data) noexcept { + return _mm256_broadcastb_epi8(_mm_cvtsi32_si128(static_cast(_Data))); + } + + static __m256i _Cmp_gt(const __m256i _First, const __m256i _Second) noexcept { + return _mm256_cmpgt_epi8(_First, _Second); + } + + static __m256i _Sign_correction(const __m256i _Data) noexcept { + return _mm256_sub_epi8(_Data, _mm256_set1_epi8(static_cast(0x80))); + } + }; + + struct _Traits_2_avx : _Traits_avx { + static __m256i _Broadcast(const uint16_t _Data) noexcept { + return _mm256_broadcastw_epi16(_mm_cvtsi32_si128(static_cast(_Data))); + } + + static __m256i _Cmp_gt(const __m256i _First, const __m256i _Second) noexcept { + return _mm256_cmpgt_epi16(_First, _Second); + } + + static __m256i _Sign_correction(const __m256i _Data) noexcept { + return _mm256_sub_epi16(_Data, _mm256_set1_epi16(static_cast(0x8000))); + } + }; + + struct _Traits_4_avx : _Traits_avx { + static __m256i _Broadcast(const uint32_t _Data) noexcept { + return _mm256_broadcastd_epi32(_mm_cvtsi32_si128(_Data)); + } + + static __m256i _Cmp_gt(const __m256i _First, const __m256i _Second) noexcept { + return _mm256_cmpgt_epi32(_First, _Second); + } + + static __m256i _Sign_correction(const __m256i _Data) noexcept { + return _mm256_sub_epi32(_Data, _mm256_set1_epi32(static_cast(0x8000'0000))); + } + }; + + struct _Traits_8_avx : _Traits_avx { + static __m256i _Broadcast(const uint64_t _Data) noexcept { +#ifdef _WIN64 + return _mm256_broadcastq_epi64(_mm_cvtsi64x_si128(_Data)); +#else // ^^^ defined(_WIN64) / !defined(_WIN64), workaround, _mm_cvtsi64x_si128 does not compile vvv + return _mm256_set1_epi64x(_Data); +#endif // ^^^ !defined(_WIN64) ^^^ + } + + static __m256i _Cmp_gt(const __m256i _First, const __m256i _Second) noexcept { + return _mm256_cmpgt_epi64(_First, _Second); + } + + static __m256i _Sign_correction(const __m256i _Data) noexcept { + return _mm256_sub_epi64(_Data, _mm256_set1_epi64x(static_cast(0x8000'0000'0000'0000))); + } + }; + + struct _Traits_sse { + using _Guard = char; + static constexpr size_t _Vec_size = 16; + static constexpr size_t _Tail_mask = 0; + + static __m128i _Load(const void* const _Src) noexcept { + return _mm_loadu_si128(reinterpret_cast(_Src)); + } + + static unsigned long _Mask(const __m128i _Val) noexcept { + return _mm_movemask_epi8(_Val); + } + + static uint32_t _Bsf(const uint32_t _Val) noexcept { + unsigned long _Index; + // CodeQL [SM02313] _Index is always initialized: _Val != 0; see explanation at call sites. + _BitScanForward(&_Index, _Val); + return _Index; + } + }; + + struct _Traits_1_sse : _Traits_sse { + static __m128i _Broadcast(const uint8_t _Data) noexcept { + return _mm_shuffle_epi8(_mm_cvtsi32_si128(static_cast(_Data)), _mm_setzero_si128()); + } + + static __m128i _Cmp_gt(const __m128i _First, const __m128i _Second) noexcept { + return _mm_cmpgt_epi8(_First, _Second); + } + + static __m128i _Sign_correction(const __m128i _Data) noexcept { + return _mm_sub_epi8(_Data, _mm_set1_epi8(static_cast(0x80))); + } + }; + + struct _Traits_2_sse : _Traits_sse { + static __m128i _Broadcast(const uint16_t _Data) noexcept { + return _mm_shuffle_epi8(_mm_cvtsi32_si128(static_cast(_Data)), _mm_set1_epi16(0x0100)); + } + + static __m128i _Cmp_gt(const __m128i _First, const __m128i _Second) noexcept { + return _mm_cmpgt_epi16(_First, _Second); + } + + static __m128i _Sign_correction(const __m128i _Data) noexcept { + return _mm_sub_epi16(_Data, _mm_set1_epi16(static_cast(0x8000))); + } + }; + + struct _Traits_4_sse : _Traits_sse { + static __m128i _Broadcast(const uint32_t _Data) noexcept { + return _mm_shuffle_epi32(_mm_cvtsi32_si128(_Data), _MM_SHUFFLE(0, 0, 0, 0)); + } + + static __m128i _Cmp_gt(const __m128i _First, const __m128i _Second) noexcept { + return _mm_cmpgt_epi32(_First, _Second); + } + + static __m128i _Sign_correction(const __m128i _Data) noexcept { + return _mm_sub_epi32(_Data, _mm_set1_epi32(static_cast(0x8000'0000))); + } + }; + + struct _Traits_8_sse : _Traits_sse { + static __m128i _Broadcast(const uint64_t _Data) noexcept { +#ifdef _WIN64 + return _mm_shuffle_epi32(_mm_cvtsi64x_si128(_Data), _MM_SHUFFLE(1, 0, 1, 0)); +#else // ^^^ defined(_WIN64) / !defined(_WIN64), workaround, _mm_cvtsi64x_si128 does not compile vvv + return _mm_set1_epi64x(_Data); +#endif // ^^^ !defined(_WIN64) ^^^ + } + + static __m128i _Cmp_gt(const __m128i _First, const __m128i _Second) noexcept { + return _mm_cmpgt_epi64(_First, _Second); + } + + static __m128i _Sign_correction(const __m128i _Data) noexcept { + return _mm_sub_epi64(_Data, _mm_set1_epi64x(static_cast(0x8000'0000'0000'0000))); + } + }; +#endif // ^^^ !defined(_M_ARM64EC) ^^^ + + template + bool _Includes_impl( + const void* _First1, const void* const _Last1, const void* _First2, const void* const _Last2) noexcept { + if constexpr (!std::is_void_v<_Traits>) { +#ifdef _M_ARM64EC + static_assert(false, "No vectorization for _M_ARM64EC yet"); +#else // ^^^ defined(_M_ARM64EC) / !defined(_M_ARM64EC) vvv + + // Only skipping some parts of haystack that are less than current needle element is vectorized. + // Otherwise this is scalar algorithm. + + constexpr bool _Is_signed = std::is_signed_v<_Ty>; + constexpr uint32_t _All_ones_mask = uint32_t{(uint64_t{1} << _Traits::_Vec_size) - 1}; + constexpr uint32_t _Highest_one_mask = 1u << (_Traits::_Vec_size - 1); + [[maybe_unused]] typename _Traits::_Guard _Guard; // TRANSITION, DevCom-10331414 + + const size_t _Size_bytes_1 = _Byte_length(_First1, _Last1); + const void* _Stop1 = _First1; + _Advance_bytes(_Stop1, _Size_bytes_1 & ~(_Traits::_Vec_size - 1)); + + _Ty _Val2 = *reinterpret_cast(_First2); + auto _Start2 = _Traits::_Broadcast(_Val2); + if constexpr (!_Is_signed) { + _Start2 = _Traits::_Sign_correction(_Start2); + } + + do { + auto _Data1 = _Traits::_Load(_First1); + if constexpr (!_Is_signed) { + _Data1 = _Traits::_Sign_correction(_Data1); + } + + const void* _Next1 = _First1; + _Advance_bytes(_Next1, _Traits::_Vec_size); + + const uint32_t _Greater_start_2 = _Traits::_Mask(_Traits::_Cmp_gt(_Start2, _Data1)); + // Testing _Highest_one_mask can be a bit more efficient on AVX2 than comparing against + // _All_ones_mask (will test sign, and can share comparison with != 0 below). + if ((_Greater_start_2 & _Highest_one_mask) != 0) { + // Needle first element is greater than each element of haystack vector. + // Proceed to the next one, without updating the needle comparand. + _First1 = _Next1; + } else { + if (_Greater_start_2 != 0) { + // Needle first element is greater than some first elements of haystack part. + // Advance past these elements. + // The input is nonzero because we handled that case with _Highest_one_mask branch above. + const uint32_t _Skip = _Traits::_Bsf(_Greater_start_2 ^ _All_ones_mask); + _Advance_bytes(_First1, _Skip); + } + + // The rest is scalar loop that completes the remaining vector-sized haystack part. + // Except that it updates current needle value to compare against. + do { + const _Ty _Val1 = *static_cast(_First1); + + if (_Val2 < _Val1) { + return false; + } + + if (_Val2 == _Val1) { + _Advance_bytes(_First2, sizeof(_Ty)); + if (_First2 == _Last2) { + return true; + } + + _Val2 = *reinterpret_cast(_First2); + } + + _Advance_bytes(_First1, sizeof(_Ty)); + } while (_First1 != _Next1); + + _Start2 = _Traits::_Broadcast(_Val2); + if constexpr (!_Is_signed) { + _Start2 = _Traits::_Sign_correction(_Start2); + } + } + } while (_First1 != _Stop1); + + if constexpr (_Traits::_Tail_mask != 0) { + const size_t _Tail_bytes_size_1 = _Size_bytes_1 & _Traits::_Tail_mask; + if (_Tail_bytes_size_1 != 0) { + // Just try to advance past less one more time. + // Don't need to repeat the scalar part here - falling to scalar loop anyway. + const auto _Tail_mask = _Avx2_tail_mask_32(_Tail_bytes_size_1); + auto _Data1 = _Traits::_Load_mask(_First1, _Tail_mask); + if constexpr (!_Is_signed) { + _Data1 = _Traits::_Sign_correction(_Data1); + } + + const auto _Cmp = _Traits::_Cmp_gt(_Start2, _Data1); + const uint32_t _Greater_start_2 = _Traits::_Mask(_mm256_and_si256(_Cmp, _Tail_mask)); + if (_Greater_start_2 != 0) { + // Needle first element is greater than some first elements of haystack part. + // Advance past these elements. + // The input is nonzero because tail mask will have zeros for remaining elements. + const uint32_t _Skip = _Traits::_Bsf(_Greater_start_2 ^ _All_ones_mask); + _Advance_bytes(_First1, _Skip); + } + } + } + + if (_First1 == _Last1) { + return false; + } +#endif // ^^^ !defined(_M_ARM64EC) ^^^ + } + + auto _Ptr1 = static_cast(_First1); + auto _Ptr2 = static_cast(_First2); + + for (;;) { + if (*_Ptr1 < *_Ptr2) { + ++_Ptr1; + if (_Ptr1 == _Last1) { + return false; + } + } else if (*_Ptr2 < *_Ptr1) { + return false; + } else { + ++_Ptr1; + ++_Ptr2; + if (_Ptr2 == _Last2) { + return true; + } else if (_Ptr1 == _Last1) { + return false; + } + } + } + } + + template + bool __stdcall _Includes_disp(const void* const _First1, const void* const _Last1, const void* const _First2, + const void* const _Last2) noexcept { + const size_t _Size_bytes_1 = _Byte_length(_First1, _Last1); + const size_t _Size_bytes_2 = _Byte_length(_First2, _Last2); + if (_Size_bytes_2 == 0) { + return true; + } else if (_Size_bytes_1 < _Size_bytes_2) { + return false; + } + +#ifndef _M_ARM64EC + if (_Size_bytes_1 >= 32 && _Use_avx2()) { + return _Includes_impl<_Traits_avx, _Ty>(_First1, _Last1, _First2, _Last2); + } + + if (_Size_bytes_1 >= 16 && _Use_sse42()) { + return _Includes_impl<_Traits_sse, _Ty>(_First1, _Last1, _First2, _Last2); + } +#endif // ^^^ !defined(_M_ARM64EC) ^^^ + return _Includes_impl(_First1, _Last1, _First2, _Last2); + } + } // namespace _Sorted_ranges +} // unnamed namespace + +extern "C" { + +__declspec(noalias) bool __stdcall __std_includes_less_1i( + const void* const _First1, const void* const _Last1, const void* const _First2, const void* const _Last2) noexcept { + return _Sorted_ranges::_Includes_disp<_Sorted_ranges::_Traits_1_avx, _Sorted_ranges::_Traits_1_sse, int8_t>( + _First1, _Last1, _First2, _Last2); +} + +__declspec(noalias) bool __stdcall __std_includes_less_1u( + const void* const _First1, const void* const _Last1, const void* const _First2, const void* const _Last2) noexcept { + return _Sorted_ranges::_Includes_disp<_Sorted_ranges::_Traits_1_avx, _Sorted_ranges::_Traits_1_sse, uint8_t>( + _First1, _Last1, _First2, _Last2); +} + +__declspec(noalias) bool __stdcall __std_includes_less_2i( + const void* const _First1, const void* const _Last1, const void* const _First2, const void* const _Last2) noexcept { + return _Sorted_ranges::_Includes_disp<_Sorted_ranges::_Traits_2_avx, _Sorted_ranges::_Traits_2_sse, int16_t>( + _First1, _Last1, _First2, _Last2); +} + +__declspec(noalias) bool __stdcall __std_includes_less_2u( + const void* const _First1, const void* const _Last1, const void* const _First2, const void* const _Last2) noexcept { + return _Sorted_ranges::_Includes_disp<_Sorted_ranges::_Traits_2_avx, _Sorted_ranges::_Traits_2_sse, uint16_t>( + _First1, _Last1, _First2, _Last2); +} + +__declspec(noalias) bool __stdcall __std_includes_less_4i( + const void* const _First1, const void* const _Last1, const void* const _First2, const void* const _Last2) noexcept { + return _Sorted_ranges::_Includes_disp<_Sorted_ranges::_Traits_4_avx, _Sorted_ranges::_Traits_4_sse, int32_t>( + _First1, _Last1, _First2, _Last2); +} + +__declspec(noalias) bool __stdcall __std_includes_less_4u( + const void* const _First1, const void* const _Last1, const void* const _First2, const void* const _Last2) noexcept { + return _Sorted_ranges::_Includes_disp<_Sorted_ranges::_Traits_4_avx, _Sorted_ranges::_Traits_4_sse, uint32_t>( + _First1, _Last1, _First2, _Last2); +} + +__declspec(noalias) bool __stdcall __std_includes_less_8i( + const void* const _First1, const void* const _Last1, const void* const _First2, const void* const _Last2) noexcept { + return _Sorted_ranges::_Includes_disp<_Sorted_ranges::_Traits_8_avx, _Sorted_ranges::_Traits_8_sse, int64_t>( + _First1, _Last1, _First2, _Last2); +} + +__declspec(noalias) bool __stdcall __std_includes_less_8u( + const void* const _First1, const void* const _Last1, const void* const _First2, const void* const _Last2) noexcept { + return _Sorted_ranges::_Includes_disp<_Sorted_ranges::_Traits_8_avx, _Sorted_ranges::_Traits_8_sse, uint64_t>( + _First1, _Last1, _First2, _Last2); +} + +} // extern "C" + namespace { namespace _Bitset_to_string { #ifdef _M_ARM64EC diff --git a/tests/std/tests/VSO_0000000_vector_algorithms/test.cpp b/tests/std/tests/VSO_0000000_vector_algorithms/test.cpp index 86c951da3f6..5cbc73d076c 100644 --- a/tests/std/tests/VSO_0000000_vector_algorithms/test.cpp +++ b/tests/std/tests/VSO_0000000_vector_algorithms/test.cpp @@ -649,6 +649,71 @@ void test_is_sorted_until(mt19937_64& gen) { } } +#if _HAS_CXX17 +template +bool last_known_good_includes(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2) { + while (first2 != last2) { + if (first1 == last1 || *first2 < *first1) { + return false; + } + + if (!(*first1 < *first2)) { + ++first2; + } + + ++first1; + } + + return true; +} + +template +void test_case_includes(const vector& hay, const vector& needle) { + const bool expected = last_known_good_includes(hay.begin(), hay.end(), needle.begin(), needle.end()); + const bool actual = includes(hay.begin(), hay.end(), needle.begin(), needle.end()); + assert(expected == actual); +#if _HAS_CXX20 + const bool actual_r = ranges::includes(hay, needle); + assert(expected == actual_r); +#endif // _HAS_CXX20 +} + +template +void test_includes(mt19937_64& gen) { + using Limits = numeric_limits; + + uniform_int_distribution> dis(Limits::min(), Limits::max()); + + vector sorted_random_data(dataCount); + generate(sorted_random_data.begin(), sorted_random_data.end(), [&dis, &gen] { return static_cast(dis(gen)); }); + sort(sorted_random_data.begin(), sorted_random_data.end()); + + vector hay; + vector needle; + hay.reserve(dataCount); + needle.reserve(dataCount + 1); + + test_case_includes(hay, needle); + + for (size_t attempts = 0; attempts < dataCount; ++attempts) { + hay.push_back(sorted_random_data[attempts]); + + uniform_int_distribution len_dis(0, hay.size()); + + for (size_t needle_length = 0; needle_length < 4; ++needle_length) { + needle.resize(len_dis(gen)); + sample(hay.begin(), hay.end(), needle.begin(), needle.size(), gen); + test_case_includes(hay, needle); + + // Look for an additional random element, typically (but not always) resulting in a negative test. + needle.push_back(static_cast(dis(gen))); + sort(needle.begin(), needle.end()); + test_case_includes(hay, needle); + } + } +} +#endif // _HAS_CXX17 + template void last_known_good_replace(FwdIt first, FwdIt last, const T old_val, const T new_val) { for (; first != last; ++first) { @@ -1209,6 +1274,19 @@ void test_vector_algorithms(mt19937_64& gen) { test_is_sorted_until(gen); test_is_sorted_until(gen); + // std::includes has been there forever, but we use std::sample in the test, and that one is C++17 +#if _HAS_CXX17 + test_includes(gen); + test_includes(gen); + test_includes(gen); + test_includes(gen); + test_includes(gen); + test_includes(gen); + test_includes(gen); + test_includes(gen); + test_includes(gen); +#endif // _HAS_CXX17 + // replace() is vectorized for 4 and 8 bytes only. test_replace(gen); test_replace(gen);