Skip to content

Commit 80cabd0

Browse files
Added fused iteration to reduce virtual dispatches (#9)
* Added fused iteration to reduce virtual dispatches * Optimization does not use a "caching iterator"
1 parent 315a242 commit 80cabd0

File tree

4 files changed

+131
-7
lines changed

4 files changed

+131
-7
lines changed

include/beman/any_view/detail/any_iterator.hpp

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#define BEMAN_ANY_VIEW_DETAIL_ANY_ITERATOR_HPP
55

66
#include <beman/any_view/concepts.hpp>
7+
#include <beman/any_view/config.hpp>
78
#include <beman/any_view/detail/intrusive_small_ptr.hpp>
89
#include <beman/any_view/detail/iterator_adaptor.hpp>
910

@@ -16,23 +17,41 @@ class any_iterator {
1617
using pointer = std::add_pointer_t<RefT>;
1718

1819
using interface_type = iterator_interface<ElementT, RefT, RValueRefT, DiffT>;
20+
using cache_type =
21+
std::conditional_t<std::derived_from<IterConceptT, std::forward_iterator_tag>, iter_cache_t<RefT>, no_cache>;
22+
23+
static constexpr bool cached = not std::same_as<cache_type, no_cache>;
24+
1925
// inplace storage sufficient for a vtable pointer and two pointers
2026
intrusive_small_ptr<interface_type, 3 * sizeof(void*)> iterator_ptr;
27+
BEMAN_ANY_VIEW_NO_UNIQUE_ADDRESS() cache_type cache;
2128

2229
template <class IteratorT, class SentinelT>
2330
static consteval auto get_in_place_adaptor_type() {
2431
return std::in_place_type<
2532
detail::iterator_adaptor<ElementT, reference, rvalue_reference, DiffT, IteratorT, SentinelT>>;
2633
}
2734

35+
template <detail::any_compatible_iterator<any_iterator> IteratorT, std::sentinel_for<IteratorT> SentinelT>
36+
constexpr any_iterator(IteratorT&& iterator, SentinelT&& sentinel, cache_type&& cache)
37+
: iterator_ptr(get_in_place_adaptor_type<IteratorT, SentinelT>(), std::move(iterator), std::move(sentinel)),
38+
cache(std::move(cache)) {}
39+
2840
public:
2941
using iterator_concept = IterConceptT;
3042
using element_type = ElementT;
3143
using difference_type = DiffT;
3244

3345
template <detail::any_compatible_iterator<any_iterator> IteratorT, std::sentinel_for<IteratorT> SentinelT>
3446
constexpr any_iterator(IteratorT iterator, SentinelT sentinel)
35-
: iterator_ptr(get_in_place_adaptor_type<IteratorT, SentinelT>(), std::move(iterator), std::move(sentinel)) {}
47+
: any_iterator(std::move(iterator), std::move(sentinel), cache_type()) {}
48+
49+
template <detail::any_compatible_iterator<any_iterator> IteratorT, std::sentinel_for<IteratorT> SentinelT>
50+
constexpr any_iterator(IteratorT iterator, SentinelT sentinel)
51+
requires(cached)
52+
: any_iterator(std::move(iterator),
53+
std::move(sentinel),
54+
iterator != sentinel ? iter_cache<RefT>::make(*iterator) : iter_cache_t<RefT>{}) {}
3655

3756
constexpr any_iterator() noexcept
3857
requires std::derived_from<IterConceptT, std::forward_iterator_tag>
@@ -50,7 +69,13 @@ class any_iterator {
5069

5170
constexpr any_iterator& operator=(any_iterator&&) noexcept = default;
5271

53-
[[nodiscard]] constexpr reference operator*() const { return **iterator_ptr; }
72+
[[nodiscard]] constexpr reference operator*() const {
73+
if constexpr (cached) {
74+
return *cache;
75+
} else {
76+
return **iterator_ptr;
77+
}
78+
}
5479

5580
[[nodiscard]] friend constexpr rvalue_reference iter_move(const any_iterator& other) {
5681
return other.iterator_ptr->iter_move();
@@ -59,11 +84,19 @@ class any_iterator {
5984
[[nodiscard]] constexpr pointer operator->() const
6085
requires std::derived_from<IterConceptT, std::contiguous_iterator_tag>
6186
{
62-
return std::to_address(*iterator_ptr);
87+
if constexpr (cached) {
88+
return cache;
89+
} else {
90+
return std::to_address(*iterator_ptr);
91+
}
6392
}
6493

6594
constexpr any_iterator& operator++() {
66-
++*iterator_ptr;
95+
if constexpr (cached) {
96+
cache = iterator_ptr->next();
97+
} else {
98+
++*iterator_ptr;
99+
}
67100
return *this;
68101
}
69102

@@ -86,7 +119,11 @@ class any_iterator {
86119
constexpr any_iterator& operator--()
87120
requires std::derived_from<IterConceptT, std::bidirectional_iterator_tag>
88121
{
89-
--*iterator_ptr;
122+
if constexpr (cached) {
123+
cache = iterator_ptr->prev();
124+
} else {
125+
--*iterator_ptr;
126+
}
90127
return *this;
91128
}
92129

@@ -113,7 +150,11 @@ class any_iterator {
113150
constexpr any_iterator& operator+=(difference_type offset)
114151
requires std::derived_from<IterConceptT, std::random_access_iterator_tag>
115152
{
116-
*iterator_ptr += offset;
153+
if constexpr (cached) {
154+
cache = iterator_ptr->next(offset);
155+
} else {
156+
*iterator_ptr += offset;
157+
}
117158
return *this;
118159
}
119160

@@ -154,7 +195,11 @@ class any_iterator {
154195
}
155196

156197
[[nodiscard]] constexpr bool operator==(std::default_sentinel_t sentinel) const {
157-
return *iterator_ptr == sentinel;
198+
if constexpr (cached) {
199+
return cache == iter_cache_t<RefT>{};
200+
} else {
201+
return *iterator_ptr == sentinel;
202+
}
158203
}
159204
};
160205

include/beman/any_view/detail/iterator_adaptor.hpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class iterator_adaptor final : public iterator_interface<ElementT, RefT, RValueR
2626
using iterator_interface = detail::iterator_interface<ElementT, RefT, RValueRefT, DiffT>;
2727
using pointer = std::add_pointer_t<RefT>;
2828

29+
static constexpr bool cached = not std::same_as<iter_cache_t<RefT>, no_cache>;
30+
2931
static constexpr auto get_noexcept() {
3032
return std::is_nothrow_move_constructible_v<IteratorT> and std::is_nothrow_move_constructible_v<SentinelT>;
3133
}
@@ -63,6 +65,30 @@ class iterator_adaptor final : public iterator_interface<ElementT, RefT, RValueR
6365
}
6466
}
6567

68+
[[nodiscard]] constexpr auto next() -> iter_cache_t<RefT> override {
69+
if constexpr (std::forward_iterator<IteratorT> and cached) {
70+
return ++iterator != sentinel ? iter_cache<RefT>::make(*iterator) : iter_cache_t<RefT>{};
71+
}
72+
73+
unreachable();
74+
}
75+
76+
[[nodiscard]] constexpr auto prev() -> iter_cache_t<RefT> override {
77+
if constexpr (std::bidirectional_iterator<IteratorT> and cached) {
78+
return --iterator != sentinel ? iter_cache<RefT>::make(*iterator) : iter_cache_t<RefT>{};
79+
}
80+
81+
unreachable();
82+
}
83+
84+
[[nodiscard]] constexpr auto next(DiffT n) -> iter_cache_t<RefT> override {
85+
if constexpr (std::random_access_iterator<IteratorT> and cached) {
86+
return (iterator += n) != sentinel ? iter_cache<RefT>::make(*iterator) : iter_cache_t<RefT>{};
87+
}
88+
89+
unreachable();
90+
}
91+
6692
[[nodiscard]] constexpr auto operator*() const -> RefT override { return *iterator; }
6793

6894
[[nodiscard]] constexpr auto iter_move() const -> RValueRefT override { return std::ranges::iter_move(iterator); }

include/beman/any_view/detail/iterator_interface.hpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,36 @@
55

66
#include <compare>
77
#include <iterator>
8+
#include <optional>
89
#include <type_traits>
910

1011
namespace beman::any_view::detail {
1112

13+
struct no_cache {};
14+
15+
template <class RefT>
16+
struct iter_cache {
17+
using type = no_cache;
18+
};
19+
20+
template <class RefT>
21+
requires std::is_trivially_copyable_v<RefT>
22+
struct iter_cache<RefT> {
23+
using type = std::optional<RefT>;
24+
25+
static constexpr auto make(RefT ref) noexcept -> type { return {ref}; }
26+
};
27+
28+
template <class RefT>
29+
struct iter_cache<RefT&> {
30+
using type = RefT*;
31+
32+
static constexpr auto make(RefT& ref) noexcept -> type { return std::addressof(ref); }
33+
};
34+
35+
template <class RefT>
36+
using iter_cache_t = iter_cache<RefT>::type;
37+
1238
template <class ElementT, class RefT, class RValueRefT, class DiffT>
1339
class iterator_interface {
1440
using pointer = std::add_pointer_t<RefT>;
@@ -21,6 +47,13 @@ class iterator_interface {
2147
// out-of-place construction
2248
[[nodiscard]] virtual constexpr auto copy() const -> iterator_interface* = 0;
2349

50+
// fused increment-compare-dereference
51+
[[nodiscard]] virtual constexpr auto next() -> iter_cache_t<RefT> = 0;
52+
53+
[[nodiscard]] virtual constexpr auto prev() -> iter_cache_t<RefT> = 0;
54+
55+
[[nodiscard]] virtual constexpr auto next(DiffT n) -> iter_cache_t<RefT> = 0;
56+
2457
// iterator methods
2558
[[nodiscard]] virtual constexpr auto operator*() const -> RefT = 0;
2659

tests/beman/any_view/constexpr.test.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,23 @@ TEST(ConstexprTest, sort_vector) {
6464
#endif
6565
EXPECT_TRUE(sort(std::vector{6, 8, 7, 5, 3, 0, 9}));
6666
}
67+
68+
constexpr auto set_front(any_view<int, forward> view, int value) {
69+
// forward iterator of lvalue reference uses cache object to fuse virtual dispatches
70+
static_assert(sizeof(std::ranges::iterator_t<any_view<int, forward>>) ==
71+
sizeof(std::ranges::iterator_t<any_view<int, input>>) + sizeof(int*));
72+
73+
auto& ref = view.front();
74+
// even with cache object, lifetime of reference is not tied to lifetime of iterator
75+
ref = value;
76+
77+
return view.front() == value;
78+
}
79+
80+
TEST(ConstexprTest, reference_lifetime) {
81+
#ifndef _MSC_VER
82+
// error C2131: expression did not evaluate to a constant
83+
static_assert(set_front(std::vector{7}, 42));
84+
#endif
85+
EXPECT_TRUE(set_front(std::vector{7}, 42));
86+
}

0 commit comments

Comments
 (0)