[libc++] Optimize ranges::{for_each, for_each_n} for segmented iterators (#132896)

Previously, the segmented iterator optimization was limited to `std::{for_each, for_each_n}`. This patch
extends the optimization to `std::ranges::for_each` and `std::ranges::for_each_n`, ensuring consistent
optimizations across these algorithms. This patch first generalizes the `std` algorithms by introducing
a `Projection` parameter, which is set to `__identity` for the `std` algorithms. Then we let the `ranges`
algorithms to directly call their `std` counterparts with a general `__proj` argument. Benchmarks
demonstrate performance improvements of up to 21.4x for ``std::deque::iterator`` and 22.3x for
``join_view`` of ``vector<vector<char>>``.

Addresses a subtask of #102817.
This commit is contained in:
Peng Liu
2025-06-18 12:22:47 -04:00
committed by GitHub
parent dd40c460c4
commit 9827440f1e
12 changed files with 197 additions and 57 deletions

View File

@@ -70,8 +70,9 @@ Improvements and New Features
- The segmented iterator optimization for ``std::for_each`` has been backported to C++11. Previously it was only available - The segmented iterator optimization for ``std::for_each`` has been backported to C++11. Previously it was only available
in C++23 and later. in C++23 and later.
- The ``std::for_each_n`` algorithm has been optimized for segmented iterators, resulting in a performance improvement of - The ``std::for_each_n``, ``std::ranges::for_each`` and ``std::ranges::for_each_n`` algorithms have been optimized for
up to 17.7x for ``std::deque<short>`` iterators, and up to 13.9x for ``std::join_view<vector<vector<short>>>`` iterators. segmented iterators, resulting in a performance improvement of up to 17.7x for ``std::deque<short>`` iterators, and up
to 13.9x for ``std::join_view<vector<vector<short>>>`` iterators.
- The ``bitset::to_string`` function has been optimized, resulting in a performance improvement of up to 8.3x for bitsets - The ``bitset::to_string`` function has been optimized, resulting in a performance improvement of up to 8.3x for bitsets
with uniformly distributed zeros and ones, and up to 13.5x and 16.1x for sparse and dense bitsets, respectively. with uniformly distributed zeros and ones, and up to 13.5x and 16.1x for sparse and dense bitsets, respectively.

View File

@@ -12,41 +12,54 @@
#include <__algorithm/for_each_segment.h> #include <__algorithm/for_each_segment.h>
#include <__config> #include <__config>
#include <__functional/identity.h>
#include <__iterator/segmented_iterator.h> #include <__iterator/segmented_iterator.h>
#include <__type_traits/enable_if.h> #include <__type_traits/enable_if.h>
#include <__type_traits/invoke.h>
#include <__utility/move.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header # pragma GCC system_header
#endif #endif
_LIBCPP_PUSH_MACROS
#include <__undef_macros>
_LIBCPP_BEGIN_NAMESPACE_STD _LIBCPP_BEGIN_NAMESPACE_STD
template <class _InputIterator, class _Sent, class _Func> template <class _InputIterator, class _Sent, class _Func, class _Proj>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __for_each(_InputIterator __first, _Sent __last, _Func& __f) { _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator
__for_each(_InputIterator __first, _Sent __last, _Func& __f, _Proj& __proj) {
for (; __first != __last; ++__first) for (; __first != __last; ++__first)
__f(*__first); std::__invoke(__f, std::__invoke(__proj, *__first));
return __first;
} }
#ifndef _LIBCPP_CXX03_LANG #ifndef _LIBCPP_CXX03_LANG
template <class _SegmentedIterator, template <class _SegmentedIterator,
class _Function, class _Func,
class _Proj,
__enable_if_t<__is_segmented_iterator<_SegmentedIterator>::value, int> = 0> __enable_if_t<__is_segmented_iterator<_SegmentedIterator>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _SegmentedIterator
__for_each(_SegmentedIterator __first, _SegmentedIterator __last, _Function& __func) { __for_each(_SegmentedIterator __first, _SegmentedIterator __last, _Func& __func, _Proj& __proj) {
using __local_iterator_t = typename __segmented_iterator_traits<_SegmentedIterator>::__local_iterator; using __local_iterator_t = typename __segmented_iterator_traits<_SegmentedIterator>::__local_iterator;
std::__for_each_segment(__first, __last, [&](__local_iterator_t __lfirst, __local_iterator_t __llast) { std::__for_each_segment(__first, __last, [&](__local_iterator_t __lfirst, __local_iterator_t __llast) {
std::__for_each(__lfirst, __llast, __func); std::__for_each(__lfirst, __llast, __func, __proj);
}); });
return __last;
} }
#endif // !_LIBCPP_CXX03_LANG #endif // !_LIBCPP_CXX03_LANG
template <class _InputIterator, class _Function> template <class _InputIterator, class _Func>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Function _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Func
for_each(_InputIterator __first, _InputIterator __last, _Function __f) { for_each(_InputIterator __first, _InputIterator __last, _Func __f) {
std::__for_each(__first, __last, __f); __identity __proj;
std::__for_each(__first, __last, __f, __proj);
return __f; return __f;
} }
_LIBCPP_END_NAMESPACE_STD _LIBCPP_END_NAMESPACE_STD
_LIBCPP_POP_MACROS
#endif // _LIBCPP___ALGORITHM_FOR_EACH_H #endif // _LIBCPP___ALGORITHM_FOR_EACH_H

View File

@@ -13,10 +13,12 @@
#include <__algorithm/for_each.h> #include <__algorithm/for_each.h>
#include <__algorithm/for_each_n_segment.h> #include <__algorithm/for_each_n_segment.h>
#include <__config> #include <__config>
#include <__functional/identity.h>
#include <__iterator/iterator_traits.h> #include <__iterator/iterator_traits.h>
#include <__iterator/segmented_iterator.h> #include <__iterator/segmented_iterator.h>
#include <__type_traits/disjunction.h> #include <__type_traits/disjunction.h>
#include <__type_traits/enable_if.h> #include <__type_traits/enable_if.h>
#include <__type_traits/invoke.h>
#include <__type_traits/negation.h> #include <__type_traits/negation.h>
#include <__utility/convert_to_integral.h> #include <__utility/convert_to_integral.h>
#include <__utility/move.h> #include <__utility/move.h>
@@ -33,16 +35,17 @@ _LIBCPP_BEGIN_NAMESPACE_STD
template <class _InputIterator, template <class _InputIterator,
class _Size, class _Size,
class _Func, class _Func,
class _Proj,
__enable_if_t<!__has_random_access_iterator_category<_InputIterator>::value && __enable_if_t<!__has_random_access_iterator_category<_InputIterator>::value &&
_Or< _Not<__is_segmented_iterator<_InputIterator> >, _Or< _Not<__is_segmented_iterator<_InputIterator> >,
_Not<__has_random_access_local_iterator<_InputIterator> > >::value, _Not<__has_random_access_local_iterator<_InputIterator> > >::value,
int> = 0> int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator
__for_each_n(_InputIterator __first, _Size __orig_n, _Func& __f) { __for_each_n(_InputIterator __first, _Size __orig_n, _Func& __f, _Proj& __proj) {
typedef decltype(std::__convert_to_integral(__orig_n)) _IntegralSize; typedef decltype(std::__convert_to_integral(__orig_n)) _IntegralSize;
_IntegralSize __n = __orig_n; _IntegralSize __n = __orig_n;
while (__n > 0) { while (__n > 0) {
__f(*__first); std::__invoke(__f, std::__invoke(__proj, *__first));
++__first; ++__first;
--__n; --__n;
} }
@@ -52,39 +55,42 @@ __for_each_n(_InputIterator __first, _Size __orig_n, _Func& __f) {
template <class _RandIter, template <class _RandIter,
class _Size, class _Size,
class _Func, class _Func,
class _Proj,
__enable_if_t<__has_random_access_iterator_category<_RandIter>::value, int> = 0> __enable_if_t<__has_random_access_iterator_category<_RandIter>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _RandIter _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _RandIter
__for_each_n(_RandIter __first, _Size __orig_n, _Func& __f) { __for_each_n(_RandIter __first, _Size __orig_n, _Func& __f, _Proj& __proj) {
typename std::iterator_traits<_RandIter>::difference_type __n = __orig_n; typename std::iterator_traits<_RandIter>::difference_type __n = __orig_n;
auto __last = __first + __n; auto __last = __first + __n;
std::__for_each(__first, __last, __f); std::__for_each(__first, __last, __f, __proj);
return std::move(__last); return __last;
} }
#ifndef _LIBCPP_CXX03_LANG #ifndef _LIBCPP_CXX03_LANG
template <class _SegmentedIterator, template <class _SegmentedIterator,
class _Size, class _Size,
class _Func, class _Func,
class _Proj,
__enable_if_t<!__has_random_access_iterator_category<_SegmentedIterator>::value && __enable_if_t<!__has_random_access_iterator_category<_SegmentedIterator>::value &&
__is_segmented_iterator<_SegmentedIterator>::value && __is_segmented_iterator<_SegmentedIterator>::value &&
__has_random_access_iterator_category< __has_random_access_iterator_category<
typename __segmented_iterator_traits<_SegmentedIterator>::__local_iterator>::value, typename __segmented_iterator_traits<_SegmentedIterator>::__local_iterator>::value,
int> = 0> int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _SegmentedIterator _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _SegmentedIterator
__for_each_n(_SegmentedIterator __first, _Size __orig_n, _Func& __f) { __for_each_n(_SegmentedIterator __first, _Size __orig_n, _Func& __f, _Proj& __proj) {
using __local_iterator_t = typename __segmented_iterator_traits<_SegmentedIterator>::__local_iterator; using __local_iterator_t = typename __segmented_iterator_traits<_SegmentedIterator>::__local_iterator;
return std::__for_each_n_segment(__first, __orig_n, [&](__local_iterator_t __lfirst, __local_iterator_t __llast) { return std::__for_each_n_segment(__first, __orig_n, [&](__local_iterator_t __lfirst, __local_iterator_t __llast) {
std::__for_each(__lfirst, __llast, __f); std::__for_each(__lfirst, __llast, __f, __proj);
}); });
} }
#endif // !_LIBCPP_CXX03_LANG #endif // !_LIBCPP_CXX03_LANG
#if _LIBCPP_STD_VER >= 17 #if _LIBCPP_STD_VER >= 17
template <class _InputIterator, class _Size, class _Function> template <class _InputIterator, class _Size, class _Func>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator
for_each_n(_InputIterator __first, _Size __orig_n, _Function __f) { for_each_n(_InputIterator __first, _Size __orig_n, _Func __f) {
return std::__for_each_n(__first, __orig_n, __f); __identity __proj;
return std::__for_each_n(__first, __orig_n, __f, __proj);
} }
#endif // _LIBCPP_STD_VER >= 17 #endif // _LIBCPP_STD_VER >= 17

View File

@@ -9,10 +9,12 @@
#ifndef _LIBCPP___ALGORITHM_RANGES_FOR_EACH_H #ifndef _LIBCPP___ALGORITHM_RANGES_FOR_EACH_H
#define _LIBCPP___ALGORITHM_RANGES_FOR_EACH_H #define _LIBCPP___ALGORITHM_RANGES_FOR_EACH_H
#include <__algorithm/for_each.h>
#include <__algorithm/for_each_n.h>
#include <__algorithm/in_fun_result.h> #include <__algorithm/in_fun_result.h>
#include <__concepts/assignable.h>
#include <__config> #include <__config>
#include <__functional/identity.h> #include <__functional/identity.h>
#include <__functional/invoke.h>
#include <__iterator/concepts.h> #include <__iterator/concepts.h>
#include <__iterator/projected.h> #include <__iterator/projected.h>
#include <__ranges/access.h> #include <__ranges/access.h>
@@ -41,9 +43,17 @@ private:
template <class _Iter, class _Sent, class _Proj, class _Func> template <class _Iter, class _Sent, class _Proj, class _Func>
_LIBCPP_HIDE_FROM_ABI constexpr static for_each_result<_Iter, _Func> _LIBCPP_HIDE_FROM_ABI constexpr static for_each_result<_Iter, _Func>
__for_each_impl(_Iter __first, _Sent __last, _Func& __func, _Proj& __proj) { __for_each_impl(_Iter __first, _Sent __last, _Func& __func, _Proj& __proj) {
for (; __first != __last; ++__first) // In the case where we have different iterator and sentinel types, the segmented iterator optimization
std::invoke(__func, std::invoke(__proj, *__first)); // in std::for_each will not kick in. Therefore, we prefer std::for_each_n in that case (whenever we can
return {std::move(__first), std::move(__func)}; // obtain the `n`).
if constexpr (!std::assignable_from<_Iter&, _Sent> && std::sized_sentinel_for<_Sent, _Iter>) {
auto __n = __last - __first;
auto __end = std::__for_each_n(std::move(__first), __n, __func, __proj);
return {std::move(__end), std::move(__func)};
} else {
auto __end = std::__for_each(std::move(__first), std::move(__last), __func, __proj);
return {std::move(__end), std::move(__func)};
}
} }
public: public:

View File

@@ -9,10 +9,10 @@
#ifndef _LIBCPP___ALGORITHM_RANGES_FOR_EACH_N_H #ifndef _LIBCPP___ALGORITHM_RANGES_FOR_EACH_N_H
#define _LIBCPP___ALGORITHM_RANGES_FOR_EACH_N_H #define _LIBCPP___ALGORITHM_RANGES_FOR_EACH_N_H
#include <__algorithm/for_each_n.h>
#include <__algorithm/in_fun_result.h> #include <__algorithm/in_fun_result.h>
#include <__config> #include <__config>
#include <__functional/identity.h> #include <__functional/identity.h>
#include <__functional/invoke.h>
#include <__iterator/concepts.h> #include <__iterator/concepts.h>
#include <__iterator/incrementable_traits.h> #include <__iterator/incrementable_traits.h>
#include <__iterator/iterator_traits.h> #include <__iterator/iterator_traits.h>
@@ -40,11 +40,8 @@ struct __for_each_n {
template <input_iterator _Iter, class _Proj = identity, indirectly_unary_invocable<projected<_Iter, _Proj>> _Func> template <input_iterator _Iter, class _Proj = identity, indirectly_unary_invocable<projected<_Iter, _Proj>> _Func>
_LIBCPP_HIDE_FROM_ABI constexpr for_each_n_result<_Iter, _Func> _LIBCPP_HIDE_FROM_ABI constexpr for_each_n_result<_Iter, _Func>
operator()(_Iter __first, iter_difference_t<_Iter> __count, _Func __func, _Proj __proj = {}) const { operator()(_Iter __first, iter_difference_t<_Iter> __count, _Func __func, _Proj __proj = {}) const {
while (__count-- > 0) { auto __last = std::__for_each_n(std::move(__first), __count, __func, __proj);
std::invoke(__func, std::invoke(__proj, *__first)); return {std::move(__last), std::move(__func)};
++__first;
}
return {std::move(__first), std::move(__func)};
} }
}; };

View File

@@ -127,6 +127,7 @@ _LIBCPP_POP_MACROS
# if !defined(_LIBCPP_REMOVE_TRANSITIVE_INCLUDES) && _LIBCPP_STD_VER <= 20 # if !defined(_LIBCPP_REMOVE_TRANSITIVE_INCLUDES) && _LIBCPP_STD_VER <= 20
# include <cstddef> # include <cstddef>
# include <iosfwd> # include <iosfwd>
# include <optional>
# include <type_traits> # include <type_traits>
# endif # endif
#endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS) #endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)

View File

@@ -504,6 +504,7 @@ _LIBCPP_POP_MACROS
# include <initializer_list> # include <initializer_list>
# include <iosfwd> # include <iosfwd>
# include <new> # include <new>
# include <optional>
# include <stdexcept> # include <stdexcept>
# include <system_error> # include <system_error>
# include <type_traits> # include <type_traits>

View File

@@ -457,6 +457,7 @@ _LIBCPP_POP_MACROS
# endif // _LIBCPP_HAS_THREADS # endif // _LIBCPP_HAS_THREADS
# if !defined(_LIBCPP_REMOVE_TRANSITIVE_INCLUDES) && _LIBCPP_STD_VER <= 20 # if !defined(_LIBCPP_REMOVE_TRANSITIVE_INCLUDES) && _LIBCPP_STD_VER <= 20
# include <optional>
# include <system_error> # include <system_error>
# endif # endif
#endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS) #endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)

View File

@@ -12,6 +12,7 @@
#include <cstddef> #include <cstddef>
#include <deque> #include <deque>
#include <list> #include <list>
#include <ranges>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -23,6 +24,7 @@ int main(int argc, char** argv) {
// {std,ranges}::for_each // {std,ranges}::for_each
{ {
auto bm = []<class Container>(std::string name, auto for_each) { auto bm = []<class Container>(std::string name, auto for_each) {
using ElemType = typename Container::value_type;
benchmark::RegisterBenchmark( benchmark::RegisterBenchmark(
name, name,
[for_each](auto& st) { [for_each](auto& st) {
@@ -33,15 +35,14 @@ int main(int argc, char** argv) {
for ([[maybe_unused]] auto _ : st) { for ([[maybe_unused]] auto _ : st) {
benchmark::DoNotOptimize(c); benchmark::DoNotOptimize(c);
auto result = for_each(first, last, [](int& x) { x = std::clamp(x, 10, 100); }); auto result = for_each(first, last, [](ElemType& x) { x = std::clamp<ElemType>(x, 10, 100); });
benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(result);
} }
}) })
->Arg(8) ->Arg(8)
->Arg(32) ->Arg(32)
->Arg(50) // non power-of-two ->Arg(50) // non power-of-two
->Arg(8192) ->Arg(8192);
->Arg(1 << 20);
}; };
bm.operator()<std::vector<int>>("std::for_each(vector<int>)", std_for_each); bm.operator()<std::vector<int>>("std::for_each(vector<int>)", std_for_each);
bm.operator()<std::deque<int>>("std::for_each(deque<int>)", std_for_each); bm.operator()<std::deque<int>>("std::for_each(deque<int>)", std_for_each);
@@ -51,6 +52,42 @@ int main(int argc, char** argv) {
bm.operator()<std::list<int>>("rng::for_each(list<int>)", std::ranges::for_each); bm.operator()<std::list<int>>("rng::for_each(list<int>)", std::ranges::for_each);
} }
// {std,ranges}::for_each for join_view
{
auto bm = []<class Container>(std::string name, auto for_each) {
using C1 = typename Container::value_type;
using ElemType = typename C1::value_type;
benchmark::RegisterBenchmark(
name,
[for_each](auto& st) {
std::size_t const size = st.range(0);
std::size_t const seg_size = 256;
std::size_t const segments = (size + seg_size - 1) / seg_size;
Container c(segments);
for (std::size_t i = 0, n = size; i < segments; ++i, n -= seg_size) {
c[i].resize(std::min(seg_size, n), ElemType(1));
}
auto view = c | std::views::join;
auto first = view.begin();
auto last = view.end();
for ([[maybe_unused]] auto _ : st) {
benchmark::DoNotOptimize(c);
auto result = for_each(first, last, [](ElemType& x) { x = std::clamp<ElemType>(x, 10, 100); });
benchmark::DoNotOptimize(result);
}
})
->Arg(8)
->Arg(32)
->Arg(50) // non power-of-two
->Arg(8192);
};
bm.operator()<std::vector<std::vector<int>>>("std::for_each(join_view(vector<vector<int>>))", std_for_each);
bm.operator()<std::vector<std::vector<int>>>("rng::for_each(join_view(vector<vector<int>>)", std::ranges::for_each);
}
benchmark::Initialize(&argc, argv); benchmark::Initialize(&argc, argv);
benchmark::RunSpecifiedBenchmarks(); benchmark::RunSpecifiedBenchmarks();
benchmark::Shutdown(); benchmark::Shutdown();

View File

@@ -21,7 +21,7 @@
int main(int argc, char** argv) { int main(int argc, char** argv) {
auto std_for_each_n = [](auto first, auto n, auto f) { return std::for_each_n(first, n, f); }; auto std_for_each_n = [](auto first, auto n, auto f) { return std::for_each_n(first, n, f); };
// std::for_each_n // {std,ranges}::for_each_n
{ {
auto bm = []<class Container>(std::string name, auto for_each_n) { auto bm = []<class Container>(std::string name, auto for_each_n) {
using ElemType = typename Container::value_type; using ElemType = typename Container::value_type;
@@ -41,19 +41,17 @@ int main(int argc, char** argv) {
->Arg(8) ->Arg(8)
->Arg(32) ->Arg(32)
->Arg(50) // non power-of-two ->Arg(50) // non power-of-two
->Arg(1024) ->Arg(8192);
->Arg(4096)
->Arg(8192)
->Arg(1 << 14)
->Arg(1 << 16)
->Arg(1 << 18);
}; };
bm.operator()<std::vector<int>>("std::for_each_n(vector<int>)", std_for_each_n); bm.operator()<std::vector<int>>("std::for_each_n(vector<int>)", std_for_each_n);
bm.operator()<std::deque<int>>("std::for_each_n(deque<int>)", std_for_each_n); bm.operator()<std::deque<int>>("std::for_each_n(deque<int>)", std_for_each_n);
bm.operator()<std::list<int>>("std::for_each_n(list<int>)", std_for_each_n); bm.operator()<std::list<int>>("std::for_each_n(list<int>)", std_for_each_n);
bm.operator()<std::vector<int>>("rng::for_each_n(vector<int>)", std::ranges::for_each_n);
bm.operator()<std::deque<int>>("rng::for_each_n(deque<int>)", std::ranges::for_each_n);
bm.operator()<std::list<int>>("rng::for_each_n(list<int>)", std::ranges::for_each_n);
} }
// std::for_each_n for join_view // {std,ranges}::for_each_n for join_view
{ {
auto bm = []<class Container>(std::string name, auto for_each_n) { auto bm = []<class Container>(std::string name, auto for_each_n) {
using C1 = typename Container::value_type; using C1 = typename Container::value_type;
@@ -81,14 +79,11 @@ int main(int argc, char** argv) {
->Arg(8) ->Arg(8)
->Arg(32) ->Arg(32)
->Arg(50) // non power-of-two ->Arg(50) // non power-of-two
->Arg(1024) ->Arg(8192);
->Arg(4096)
->Arg(8192)
->Arg(1 << 14)
->Arg(1 << 16)
->Arg(1 << 18);
}; };
bm.operator()<std::vector<std::vector<int>>>("std::for_each_n(join_view(vector<vector<int>>))", std_for_each_n); bm.operator()<std::vector<std::vector<int>>>("std::for_each_n(join_view(vector<vector<int>>))", std_for_each_n);
bm.operator()<std::vector<std::vector<int>>>(
"rng::for_each_n(join_view(vector<vector<int>>)", std::ranges::for_each_n);
} }
benchmark::Initialize(&argc, argv); benchmark::Initialize(&argc, argv);

View File

@@ -20,7 +20,10 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cassert>
#include <deque>
#include <ranges> #include <ranges>
#include <vector>
#include "almost_satisfies_types.h" #include "almost_satisfies_types.h"
#include "test_iterators.h" #include "test_iterators.h"
@@ -30,7 +33,7 @@ struct Callable {
}; };
template <class Iter, class Sent = Iter> template <class Iter, class Sent = Iter>
concept HasForEachIt = requires (Iter iter, Sent sent) { std::ranges::for_each(iter, sent, Callable{}); }; concept HasForEachIt = requires(Iter iter, Sent sent) { std::ranges::for_each(iter, sent, Callable{}); };
static_assert(HasForEachIt<int*>); static_assert(HasForEachIt<int*>);
static_assert(!HasForEachIt<InputIteratorNotDerivedFrom>); static_assert(!HasForEachIt<InputIteratorNotDerivedFrom>);
@@ -47,7 +50,7 @@ static_assert(!HasForEachItFunc<IndirectUnaryPredicateNotPredicate>);
static_assert(!HasForEachItFunc<IndirectUnaryPredicateNotCopyConstructible>); static_assert(!HasForEachItFunc<IndirectUnaryPredicateNotCopyConstructible>);
template <class Range> template <class Range>
concept HasForEachR = requires (Range range) { std::ranges::for_each(range, Callable{}); }; concept HasForEachR = requires(Range range) { std::ranges::for_each(range, Callable{}); };
static_assert(HasForEachR<UncheckedRange<int*>>); static_assert(HasForEachR<UncheckedRange<int*>>);
static_assert(!HasForEachR<InputRangeNotDerivedFrom>); static_assert(!HasForEachR<InputRangeNotDerivedFrom>);
@@ -68,7 +71,7 @@ constexpr void test_iterator() {
{ // simple test { // simple test
{ {
auto func = [i = 0](int& a) mutable { a += i++; }; auto func = [i = 0](int& a) mutable { a += i++; };
int a[] = {1, 6, 3, 4}; int a[] = {1, 6, 3, 4};
std::same_as<std::ranges::for_each_result<Iter, decltype(func)>> decltype(auto) ret = std::same_as<std::ranges::for_each_result<Iter, decltype(func)>> decltype(auto) ret =
std::ranges::for_each(Iter(a), Sent(Iter(a + 4)), func); std::ranges::for_each(Iter(a), Sent(Iter(a + 4)), func);
assert(a[0] == 1); assert(a[0] == 1);
@@ -81,8 +84,8 @@ constexpr void test_iterator() {
assert(i == 4); assert(i == 4);
} }
{ {
auto func = [i = 0](int& a) mutable { a += i++; }; auto func = [i = 0](int& a) mutable { a += i++; };
int a[] = {1, 6, 3, 4}; int a[] = {1, 6, 3, 4};
auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 4))); auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 4)));
std::same_as<std::ranges::for_each_result<Iter, decltype(func)>> decltype(auto) ret = std::same_as<std::ranges::for_each_result<Iter, decltype(func)>> decltype(auto) ret =
std::ranges::for_each(range, func); std::ranges::for_each(range, func);
@@ -110,6 +113,30 @@ constexpr void test_iterator() {
} }
} }
struct deque_test {
std::deque<int>* d_;
int* i_;
deque_test(std::deque<int>& d, int& i) : d_(&d), i_(&i) {}
void operator()(int& v) {
assert(&(*d_)[*i_] == &v);
++*i_;
}
};
/*TEST_CONSTEXPR_CXX26*/
void test_segmented_deque_iterator() { // TODO: Mark as TEST_CONSTEXPR_CXX26 once std::deque is constexpr
// check that segmented deque iterators work properly
int sizes[] = {0, 1, 2, 1023, 1024, 1025, 2047, 2048, 2049};
for (const int size : sizes) {
std::deque<int> d(size);
int index = 0;
std::ranges::for_each(d, deque_test(d, index));
}
}
constexpr bool test() { constexpr bool test() {
test_iterator<cpp17_input_iterator<int*>, sentinel_wrapper<cpp17_input_iterator<int*>>>(); test_iterator<cpp17_input_iterator<int*>, sentinel_wrapper<cpp17_input_iterator<int*>>>();
test_iterator<cpp20_input_iterator<int*>, sentinel_wrapper<cpp20_input_iterator<int*>>>(); test_iterator<cpp20_input_iterator<int*>, sentinel_wrapper<cpp20_input_iterator<int*>>>();
@@ -146,6 +173,15 @@ constexpr bool test() {
} }
} }
if (!TEST_IS_CONSTANT_EVALUATED) // TODO: Use TEST_STD_AT_LEAST_26_OR_RUNTIME_EVALUATED when std::deque is made constexpr
test_segmented_deque_iterator();
{
std::vector<std::vector<int>> vec = {{0}, {1, 2}, {3, 4, 5}, {6, 7, 8, 9}, {10}, {11, 12, 13}};
auto v = vec | std::views::join;
std::ranges::for_each(v, [i = 0](int x) mutable { assert(x == 2 * i++); }, [](int x) { return 2 * x; });
}
return true; return true;
} }

View File

@@ -17,7 +17,12 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cassert>
#include <deque>
#include <iterator>
#include <ranges> #include <ranges>
#include <ranges>
#include <vector>
#include "almost_satisfies_types.h" #include "almost_satisfies_types.h"
#include "test_iterators.h" #include "test_iterators.h"
@@ -27,7 +32,7 @@ struct Callable {
}; };
template <class Iter> template <class Iter>
concept HasForEachN = requires (Iter iter) { std::ranges::for_each_n(iter, 0, Callable{}); }; concept HasForEachN = requires(Iter iter) { std::ranges::for_each_n(iter, 0, Callable{}); };
static_assert(HasForEachN<int*>); static_assert(HasForEachN<int*>);
static_assert(!HasForEachN<InputIteratorNotDerivedFrom>); static_assert(!HasForEachN<InputIteratorNotDerivedFrom>);
@@ -45,7 +50,7 @@ template <class Iter>
constexpr void test_iterator() { constexpr void test_iterator() {
{ // simple test { // simple test
auto func = [i = 0](int& a) mutable { a += i++; }; auto func = [i = 0](int& a) mutable { a += i++; };
int a[] = {1, 6, 3, 4}; int a[] = {1, 6, 3, 4};
std::same_as<std::ranges::for_each_result<Iter, decltype(func)>> auto ret = std::same_as<std::ranges::for_each_result<Iter, decltype(func)>> auto ret =
std::ranges::for_each_n(Iter(a), 4, func); std::ranges::for_each_n(Iter(a), 4, func);
assert(a[0] == 1); assert(a[0] == 1);
@@ -64,6 +69,30 @@ constexpr void test_iterator() {
} }
} }
struct deque_test {
std::deque<int>* d_;
int* i_;
deque_test(std::deque<int>& d, int& i) : d_(&d), i_(&i) {}
void operator()(int& v) {
assert(&(*d_)[*i_] == &v);
++*i_;
}
};
/*TEST_CONSTEXPR_CXX26*/
void test_segmented_deque_iterator() { // TODO: Mark as TEST_CONSTEXPR_CXX26 once std::deque is constexpr
// check that segmented deque iterators work properly
int sizes[] = {0, 1, 2, 1023, 1024, 1025, 2047, 2048, 2049};
for (const int size : sizes) {
std::deque<int> d(size);
int index = 0;
std::ranges::for_each_n(d.begin(), d.size(), deque_test(d, index));
}
}
constexpr bool test() { constexpr bool test() {
test_iterator<cpp17_input_iterator<int*>>(); test_iterator<cpp17_input_iterator<int*>>();
test_iterator<cpp20_input_iterator<int*>>(); test_iterator<cpp20_input_iterator<int*>>();
@@ -89,6 +118,19 @@ constexpr bool test() {
assert(a[2].other == 6); assert(a[2].other == 6);
} }
if (!TEST_IS_CONSTANT_EVALUATED) // TODO: Use TEST_STD_AT_LEAST_26_OR_RUNTIME_EVALUATED when std::deque is made constexpr
test_segmented_deque_iterator();
{
std::vector<std::vector<int>> vec = {{0}, {1, 2}, {3, 4, 5}, {6, 7, 8, 9}, {10}, {11, 12, 13}};
auto v = vec | std::views::join;
std::ranges::for_each_n(
v.begin(),
std::ranges::distance(v),
[i = 0](int x) mutable { assert(x == 2 * i++); },
[](int x) { return 2 * x; });
}
return true; return true;
} }