Skip to content

Commit 8e8fd63

Browse files
Merge pull request #1148 from PowerGridModel/feature/iterator-facade-deducing-this
Modernize internal implementation: iterator facade deducing this
2 parents 7c146d3 + 4185c7d commit 8e8fd63

File tree

6 files changed

+403
-140
lines changed

6 files changed

+403
-140
lines changed

power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/dataset.hpp

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,28 +117,38 @@ class ColumnarAttributeRange : public std::ranges::view_interface<ColumnarAttrib
117117
std::span<AttributeBuffer<Data> const> attribute_buffers_{};
118118
};
119119

120-
class iterator
121-
: public IteratorFacade<iterator, std::conditional_t<is_data_mutable_v<dataset_type>, Proxy, Proxy const>,
122-
Idx> {
120+
class iterator : public IteratorFacade {
123121
public:
124-
using value_type = Proxy;
122+
using value_type = std::conditional_t<is_data_mutable_v<dataset_type>, Proxy, Proxy const>;
125123
using difference_type = Idx;
124+
using pointer = std::add_pointer_t<value_type>;
125+
using reference = std::add_lvalue_reference_t<value_type>;
126126

127-
iterator() = default;
127+
iterator() : IteratorFacade{*this} {};
128128
iterator(difference_type idx, std::span<AttributeBuffer<Data> const> attribute_buffers)
129-
: current_{idx, attribute_buffers} {}
129+
: IteratorFacade{*this}, current_{idx, attribute_buffers} {}
130130

131-
private:
132-
friend class IteratorFacade<iterator, std::conditional_t<is_data_mutable_v<dataset_type>, Proxy, Proxy const>,
133-
Idx>;
134-
135-
constexpr auto dereference() -> value_type& { return current_; }
136-
constexpr auto dereference() const -> std::add_lvalue_reference_t<std::add_const_t<value_type>> {
131+
constexpr auto operator*() -> reference { return current_; }
132+
constexpr auto operator*() const -> std::add_lvalue_reference_t<std::add_const_t<value_type>> {
137133
return current_;
138134
}
139-
constexpr auto three_way_compare(iterator const& other) const { return current_.idx_ <=> other.current_.idx_; }
140-
constexpr auto distance_to(iterator const& other) const { return other.current_.idx_ - current_.idx_; }
141-
constexpr void advance(difference_type n) { current_.idx_ += n; }
135+
136+
friend constexpr auto operator<=>(iterator const& first, iterator const& second) {
137+
return first.current_idx() <=> second.current_idx();
138+
}
139+
140+
constexpr auto operator+=(difference_type n) -> std::add_lvalue_reference_t<iterator> {
141+
current_idx() += n;
142+
return *this;
143+
}
144+
145+
friend constexpr auto operator-(iterator const& first, iterator const& second) -> difference_type {
146+
return first.current_idx() - second.current_idx();
147+
}
148+
149+
private:
150+
constexpr auto current_idx() const { return current_.idx_; }
151+
constexpr auto& current_idx() { return current_.idx_; }
142152

143153
Proxy current_;
144154
};

power_grid_model_c/power_grid_model/include/power_grid_model/common/grouped_index_vector.hpp

Lines changed: 72 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -94,26 +94,20 @@ constexpr auto from_dense = from_dense_t{};
9494

9595
class SparseGroupedIdxVector {
9696
private:
97-
class GroupIterator : public IteratorFacade<GroupIterator, IdxRange const, Idx> {
98-
friend class IteratorFacade<GroupIterator, IdxRange const, Idx>;
99-
using Base = IteratorFacade<GroupIterator, IdxRange const, Idx>;
100-
97+
class GroupIterator : public IteratorFacade {
10198
public:
102-
using value_type = typename Base::value_type;
103-
using difference_type = typename Base::difference_type;
104-
using reference = typename Base::reference;
105-
106-
GroupIterator() = default;
107-
explicit constexpr GroupIterator(IdxVector const& indptr, Idx group) : indptr_{&indptr}, group_{group} {}
108-
109-
private:
110-
IdxVector const* indptr_{};
111-
Idx group_{};
112-
mutable value_type latest_value_{}; // making this mutable allows us to delay out-of-bounds checks until
113-
// dereferencing instead of update methods. Note that the value will be
114-
// invalidated at first update
115-
116-
constexpr auto dereference() const -> reference {
99+
using iterator = GroupIterator;
100+
using const_iterator = std::add_const_t<GroupIterator>;
101+
using value_type = IdxRange const;
102+
using difference_type = Idx;
103+
using pointer = std::add_pointer_t<value_type>;
104+
using reference = std::add_lvalue_reference_t<value_type>;
105+
106+
GroupIterator() : IteratorFacade{*this} {};
107+
explicit constexpr GroupIterator(IdxVector const& indptr, Idx group)
108+
: IteratorFacade{*this}, indptr_{&indptr}, group_{group} {}
109+
110+
constexpr auto operator*() const -> reference {
117111
assert(indptr_ != nullptr);
118112
assert(0 <= group_);
119113
assert(group_ < static_cast<Idx>(indptr_->size() - 1));
@@ -123,15 +117,29 @@ class SparseGroupedIdxVector {
123117
latest_value_ = value_type{(*indptr_)[group_], (*indptr_)[group_ + 1]};
124118
return latest_value_;
125119
}
126-
constexpr std::strong_ordering three_way_compare(GroupIterator const& other) const {
127-
assert(indptr_ == other.indptr_);
128-
return group_ <=> other.group_;
120+
121+
friend constexpr std::strong_ordering operator<=>(GroupIterator const& first, GroupIterator const& second) {
122+
assert(first.indptr_ == second.indptr_);
123+
return first.group_ <=> second.group_;
124+
}
125+
126+
constexpr auto operator+=(difference_type n) -> std::add_lvalue_reference_t<GroupIterator> {
127+
group_ += n;
128+
return *this;
129129
}
130-
constexpr auto distance_to(GroupIterator const& other) const {
131-
assert(indptr_ == other.indptr_);
132-
return other.group_ - group_;
130+
131+
friend auto operator-(GroupIterator const& first, GroupIterator const& second) -> difference_type {
132+
assert(first.indptr_ == second.indptr_);
133+
return first.group_ - second.group_;
133134
}
134-
constexpr void advance(Idx n) { group_ += n; }
135+
136+
private:
137+
IdxVector const* indptr_{};
138+
Idx group_{};
139+
mutable std::remove_const_t<value_type>
140+
latest_value_{}; // making this mutable allows us to delay out-of-bounds checks until
141+
// dereferencing instead of update methods. Note that the value will be
142+
// invalidated at first update
135143
};
136144

137145
constexpr auto group_iterator(Idx group) const { return GroupIterator{indptr_, group}; }
@@ -168,32 +176,25 @@ class SparseGroupedIdxVector {
168176

169177
class DenseGroupedIdxVector {
170178
private:
171-
class GroupIterator : public IteratorFacade<GroupIterator, IdxRange const, Idx> {
172-
friend class IteratorFacade<GroupIterator, IdxRange const, Idx>;
173-
using Base = IteratorFacade<GroupIterator, IdxRange const, Idx>;
179+
class GroupIterator : public IteratorFacade {
180+
friend class IteratorFacade; // to expose increment and decrement
174181

175182
public:
176-
using value_type = typename Base::value_type;
177-
using difference_type = typename Base::difference_type;
178-
using reference = typename Base::reference;
179-
180-
GroupIterator() = default;
183+
using iterator = GroupIterator;
184+
using const_iterator = std::add_const_t<GroupIterator>;
185+
using value_type = IdxRange const;
186+
using difference_type = Idx;
187+
using pointer = std::add_pointer_t<value_type>;
188+
using reference = std::add_lvalue_reference_t<value_type>;
189+
190+
GroupIterator() : IteratorFacade{*this} {};
181191
explicit constexpr GroupIterator(IdxVector const& dense_vector, Idx group)
182-
: dense_vector_{&dense_vector},
192+
: IteratorFacade{*this},
193+
dense_vector_{&dense_vector},
183194
group_{group},
184195
group_range_{std::ranges::equal_range(*dense_vector_, group)} {}
185196

186-
private:
187-
using group_iterator = IdxVector::const_iterator;
188-
189-
IdxVector const* dense_vector_{};
190-
Idx group_{};
191-
std::ranges::subrange<group_iterator> group_range_;
192-
mutable value_type latest_value_{}; // making this mutable allows us to delay out-of-bounds checks until
193-
// dereferencing instead of update methods. Note that the value will be
194-
// invalidated at first update
195-
196-
constexpr auto dereference() const -> reference {
197+
constexpr auto operator*() const -> reference {
197198
assert(dense_vector_ != nullptr);
198199

199200
// delaying out-of-bounds checking until dereferencing while still returning a reference type requires
@@ -203,10 +204,31 @@ class DenseGroupedIdxVector {
203204
narrow_cast<Idx>(std::distance(std::cbegin(*dense_vector_), group_range_.end()))};
204205
return latest_value_;
205206
}
206-
constexpr std::strong_ordering three_way_compare(GroupIterator const& other) const {
207-
return group_ <=> other.group_;
207+
208+
friend constexpr std::strong_ordering operator<=>(GroupIterator const& first, GroupIterator const& second) {
209+
assert(first.dense_vector_ == second.dense_vector_);
210+
return first.group_ <=> second.group_;
211+
}
212+
213+
constexpr auto operator+=(difference_type n) -> std::add_lvalue_reference_t<GroupIterator> {
214+
advance(n);
215+
return *this;
208216
}
209-
constexpr auto distance_to(GroupIterator const& other) const { return other.group_ - group_; }
217+
friend auto operator-(GroupIterator const& first, GroupIterator const& second) -> difference_type {
218+
assert(first.dense_vector_ == second.dense_vector_);
219+
return first.group_ - second.group_;
220+
}
221+
222+
private:
223+
using group_iterator = IdxVector::const_iterator;
224+
225+
IdxVector const* dense_vector_{};
226+
Idx group_{};
227+
std::ranges::subrange<group_iterator> group_range_;
228+
mutable std::remove_const_t<value_type>
229+
latest_value_{}; // making this mutable allows us to delay out-of-bounds checks until
230+
// dereferencing instead of update methods. Note that the value will be
231+
// invalidated at first update
210232

211233
constexpr void increment() {
212234
++group_;
@@ -220,7 +242,7 @@ class DenseGroupedIdxVector {
220242
.base(),
221243
group_range_.begin()};
222244
}
223-
constexpr void advance(Idx n) {
245+
constexpr void advance(difference_type n) {
224246
auto const start = n > 0 ? group_range_.end() : std::cbegin(*dense_vector_);
225247
auto const stop = n < 0 ? group_range_.begin() : std::cend(*dense_vector_);
226248

power_grid_model_c/power_grid_model/include/power_grid_model/common/iterator_facade.hpp

Lines changed: 80 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -8,93 +8,113 @@
88

99
#include <concepts>
1010
#include <ranges>
11+
#include <utility>
1112

1213
namespace power_grid_model {
13-
template <class Impl, typename ValueType, std::integral DifferenceType> class IteratorFacade {
14+
namespace detail {
15+
template <typename T>
16+
concept iterator_facadeable_c = std::integral<typename T::difference_type> && std::is_pointer_v<typename T::pointer> &&
17+
std::is_lvalue_reference_v<typename T::reference> &&
18+
requires(T t, std::remove_cvref_t<T> mt, std::add_const_t<T> ct,
19+
std::add_const_t<T> ct2, typename std::remove_cvref_t<T>::difference_type d) {
20+
typename T::value_type;
21+
{ *t } -> std::same_as<typename T::reference>;
22+
{ &*t } -> std::same_as<typename T::pointer>;
23+
{ ct <=> ct2 } -> std::same_as<std::strong_ordering>;
24+
{ mt += d } -> std::same_as<std::add_lvalue_reference_t<T>>;
25+
{ ct - ct2 } -> std::same_as<typename T::difference_type>;
26+
};
27+
28+
template <typename T, typename Int>
29+
concept compound_assignable_c = requires(T t, Int n) {
30+
{ t += n } -> std::same_as<std::add_lvalue_reference_t<T>>;
31+
};
32+
} // namespace detail
33+
34+
class IteratorFacade {
1435
public:
15-
using iterator = Impl; // CRTP
16-
using const_iterator = std::add_const_t<iterator>;
17-
using value_type = std::remove_cvref_t<ValueType>;
18-
using difference_type = DifferenceType;
1936
using iterator_category = std::random_access_iterator_tag;
20-
using pointer = std::add_pointer_t<ValueType>;
21-
using reference = std::add_lvalue_reference_t<ValueType>;
2237

23-
constexpr auto operator*() const -> decltype(auto) { return static_cast<const_iterator*>(this)->dereference(); }
24-
constexpr auto operator*() -> reference
25-
requires requires(iterator it) {
26-
{ it.dereference() } -> std::same_as<reference>;
27-
}
28-
{
29-
return static_cast<iterator*>(this)->dereference();
38+
template <typename Self> constexpr decltype(auto) operator->(this Self&& self) {
39+
return &(*std::forward<Self>(self));
3040
}
31-
constexpr auto operator->() const -> decltype(auto) { return &(*(*this)); }
32-
constexpr auto operator->() -> decltype(auto) { return &(*(*this)); }
3341

34-
friend constexpr bool operator==(IteratorFacade const& first, IteratorFacade const& second) {
35-
return (first <=> second) == std::strong_ordering::equivalent;
36-
}
37-
friend constexpr std::strong_ordering operator<=>(IteratorFacade const& first, IteratorFacade const& second) {
38-
return first.three_way_compare(second);
42+
template <typename Self, typename Other>
43+
requires std::same_as<std::remove_cvref_t<Self>, std::remove_cvref_t<Other>>
44+
constexpr bool operator==(this Self const& self, Other const& other) {
45+
return (self <=> other) == std::strong_ordering::equivalent;
3946
}
4047

41-
constexpr auto operator++() -> iterator& {
42-
if constexpr (requires(iterator it) { it.increment(); }) {
43-
static_cast<iterator*>(this)->increment();
48+
// NOLINTNEXTLINE(cert-dcl21-cpp) // pre-decrement but clang-tidy incorrectly sees this as post-decrement
49+
template <typename Self> constexpr std::add_lvalue_reference_t<Self> operator++(this Self& self) {
50+
if constexpr (requires { self.increment(); }) { // NOTE: IteratorFacade should be a friend class
51+
self.increment();
4452
} else {
45-
static_cast<iterator*>(this)->advance(1);
53+
return (self += 1);
4654
}
47-
return *static_cast<iterator*>(this);
55+
return self;
4856
}
49-
constexpr auto operator--() -> iterator& {
50-
if constexpr (requires(iterator it) { it.decrement(); }) {
51-
static_cast<iterator*>(this)->decrement();
57+
// NOLINTNEXTLINE(cert-dcl21-cpp) // pre-decrement but clang-tidy incorrectly sees this as post-decrement
58+
template <typename Self> constexpr std::add_lvalue_reference_t<Self> operator--(this Self& self) {
59+
if constexpr (requires { self.decrement(); }) { // NOTE: IteratorFacade should be a friend class
60+
self.decrement();
5261
} else {
53-
static_cast<iterator*>(this)->advance(-1);
62+
return (self += -1);
5463
}
55-
return *static_cast<iterator*>(this);
64+
return self;
5665
}
57-
constexpr auto operator++(std::integral auto /*idx*/) -> iterator {
58-
iterator result{*static_cast<iterator*>(this)};
59-
++(*this);
66+
template <typename Self>
67+
constexpr std::remove_cvref_t<Self> operator++(this Self& self, std::integral auto /*idx*/) {
68+
using Result = std::remove_cvref_t<Self>;
69+
Result result{self};
70+
++self;
6071
return result;
6172
}
62-
constexpr auto operator--(std::integral auto /*idx*/) -> iterator {
63-
iterator result{*static_cast<iterator*>(this)};
64-
--(*this);
73+
template <typename Self>
74+
constexpr std::remove_cvref_t<Self> operator--(this Self& self, std::integral auto /*idx*/) {
75+
using Result = std::remove_cvref_t<Self>;
76+
Result result{self};
77+
--self;
6578
return result;
6679
}
67-
constexpr auto operator+=(std::integral auto offset) -> iterator& {
68-
static_cast<iterator*>(this)->advance(offset);
69-
return *static_cast<iterator*>(this);
80+
template <typename Self>
81+
constexpr std::add_lvalue_reference_t<Self> operator-=(this Self& self, std::integral auto idx) {
82+
return (self += (-idx));
7083
}
71-
constexpr auto operator-=(std::integral auto idx) -> iterator& { return ((*this) += (-idx)); }
7284

73-
friend constexpr auto operator+(iterator const& it, difference_type offset) -> iterator {
74-
iterator result{it};
85+
template <typename Self, std::integral Int>
86+
friend constexpr std::remove_cvref_t<Self> operator+(Self&& self, Int offset)
87+
requires std::derived_from<std::remove_cvref_t<Self>, IteratorFacade> //&& detail::iterator_facadeable_c<Self>
88+
{
89+
using Result = std::remove_cvref_t<Self>;
90+
Result result{std::forward<Self>(self)};
7591
result += offset;
7692
return result;
7793
}
78-
friend constexpr auto operator+(difference_type offset, iterator it) -> iterator { return (it += offset); }
79-
friend constexpr auto operator-(iterator const& it, difference_type idx) -> iterator { return it + (-idx); }
80-
friend constexpr auto operator-(IteratorFacade const& first, IteratorFacade const& second) -> difference_type {
81-
return second.distance_to(first);
94+
template <typename Self, std::integral Int>
95+
requires std::derived_from<std::remove_cvref_t<Self>, IteratorFacade> //&& detail::iterator_facadeable_c<Self>
96+
friend constexpr std::remove_cvref_t<Self> operator+(Int offset, Self&& self) {
97+
return std::forward<Self>(self) + offset;
98+
}
99+
template <typename Self, std::integral Int>
100+
requires std::derived_from<std::remove_cvref_t<Self>, IteratorFacade> //&& detail::iterator_facadeable_c<Self>
101+
friend constexpr std::remove_cvref_t<Self> operator-(Self&& self, Int idx) {
102+
return (std::forward<Self>(self)) + (-idx);
82103
}
83104

84-
constexpr auto operator[](difference_type idx) const -> value_type const& { return *(*this + idx); }
85-
86-
private:
87-
IteratorFacade() = default; // default constructor is private to prevent non-CRTP instantiation
88-
friend Impl; // allow Impl to access private members; this is necessary for CRTP
89-
90-
// overloads for public bidirectional exposure (difference between MSVC and ClangCL)
91-
constexpr std::strong_ordering three_way_compare(IteratorFacade const& other) const {
92-
return static_cast<std::add_lvalue_reference_t<const_iterator>>(*this).three_way_compare(
93-
static_cast<std::add_lvalue_reference_t<const_iterator>>(other));
105+
template <typename Self>
106+
constexpr decltype(auto) operator[](this Self const& self, typename Self::difference_type idx) {
107+
return *(self + idx);
94108
}
95-
constexpr auto distance_to(IteratorFacade const& other) const -> difference_type {
96-
return static_cast<std::add_lvalue_reference_t<const_iterator>>(*this).distance_to(
97-
static_cast<std::add_lvalue_reference_t<const_iterator>>(other));
109+
110+
// prevent construction by non-derived and non-iterator-facadeable types
111+
IteratorFacade() = delete;
112+
template <typename Self> constexpr explicit IteratorFacade(Self& /*self*/) {
113+
// cannot be done using constraints because the type is not fully instantiated yet when the compiler
114+
// instantiates the constructor. Note that this is different from the other methods because those are only
115+
// instantiated when used.
116+
static_assert(std::derived_from<std::remove_cvref_t<Self>, IteratorFacade>);
117+
static_assert(detail::iterator_facadeable_c<std::remove_cvref_t<Self>>);
98118
}
99119
};
100120

0 commit comments

Comments
 (0)