Skip to content

Commit 1b651be

Browse files
committed
ADT: Fix const-correctness of iterator adaptors
This fixes const-correctness of iterator adaptors, dropping non-`const` overloads for `operator*()`. Iterators, like the pointers that they generalize, have two types of `const`. The `const` qualifier on members indicates whether the iterator itself can be changed. This is analagous to `int *const`. The `const` qualifier on return values of `operator*()`, `operator[]()`, and `operator->()` controls whether the the pointed-to value can be changed. This is analogous to `const int *`. Since `operator*()` does not (in principle) change the iterator, then there should only be one definition, which is `const`-qualified. E.g., iterators wrapping `int*` should look like: ``` int *operator*() const; // always const-qualified, no overloads ``` ba7a6b3 changed `iterator_adaptor_base` away from this to work around bugs in other iterator adaptors. That was already reverted. This patch adds back its test, which combined llvm::enumerate() and llvm::make_filter_range(), adds a test for iterator_adaptor_base itself, and cleans up the `const`-ness of the other iterator adaptors. This also updates the documented requirements for `iterator_facade_base`: ``` /// OLD: /// - const T &operator*() const; /// - T &operator*(); /// New: /// - T &operator*() const; ``` In a future commit we might also clean up `iterator_facade`'s overloads of `operator->()` and `operator[]()`. These already (correctly) return non-`const` proxies regardless of the iterator's `const` qualifier. Differential Revision: https://reviews.llvm.org/D113158
1 parent a1b496b commit 1b651be

File tree

3 files changed

+100
-17
lines changed

3 files changed

+100
-17
lines changed

llvm/include/llvm/ADT/STLExtras.h

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -671,9 +671,7 @@ struct zip_common : public zip_traits<ZipType, Iters...> {
671671
public:
672672
zip_common(Iters &&... ts) : iterators(std::forward<Iters>(ts)...) {}
673673

674-
value_type operator*() { return deref(std::index_sequence_for<Iters...>{}); }
675-
676-
const value_type operator*() const {
674+
value_type operator*() const {
677675
return deref(std::index_sequence_for<Iters...>{});
678676
}
679677

@@ -844,8 +842,6 @@ class zip_longest_iterator
844842
: iterators(std::forward<Iters>(ts.first)...),
845843
end_iterators(std::forward<Iters>(ts.second)...) {}
846844

847-
value_type operator*() { return deref(std::index_sequence_for<Iters...>{}); }
848-
849845
value_type operator*() const {
850846
return deref(std::index_sequence_for<Iters...>{});
851847
}
@@ -1939,8 +1935,7 @@ template <typename R> struct result_pair {
19391935
}
19401936

19411937
std::size_t index() const { return Index; }
1942-
const value_reference value() const { return *Iter; }
1943-
value_reference value() { return *Iter; }
1938+
value_reference value() const { return *Iter; }
19441939

19451940
private:
19461941
std::size_t Index = std::numeric_limits<std::size_t>::max();
@@ -1949,11 +1944,8 @@ template <typename R> struct result_pair {
19491944

19501945
template <typename R>
19511946
class enumerator_iter
1952-
: public iterator_facade_base<
1953-
enumerator_iter<R>, std::forward_iterator_tag, result_pair<R>,
1954-
typename std::iterator_traits<IterOfRange<R>>::difference_type,
1955-
typename std::iterator_traits<IterOfRange<R>>::pointer,
1956-
typename std::iterator_traits<IterOfRange<R>>::reference> {
1947+
: public iterator_facade_base<enumerator_iter<R>, std::forward_iterator_tag,
1948+
const result_pair<R>> {
19571949
using result_type = result_pair<R>;
19581950

19591951
public:
@@ -1963,7 +1955,6 @@ class enumerator_iter
19631955
enumerator_iter(std::size_t Index, IterOfRange<R> Iter)
19641956
: Result(Index, Iter) {}
19651957

1966-
result_type &operator*() { return Result; }
19671958
const result_type &operator*() const { return Result; }
19681959

19691960
enumerator_iter &operator++() {

llvm/include/llvm/ADT/iterator.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ namespace llvm {
4242
/// (All of the following methods)
4343
/// - DerivedT &operator=(const DerivedT &R);
4444
/// - bool operator==(const DerivedT &R) const;
45-
/// - const T &operator*() const;
46-
/// - T &operator*();
45+
/// - T &operator*() const;
4746
/// - DerivedT &operator++();
4847
///
4948
/// Bidirectional Iterators:
@@ -348,8 +347,7 @@ class pointer_iterator
348347
explicit pointer_iterator(WrappedIteratorT u)
349348
: pointer_iterator::iterator_adaptor_base(std::move(u)) {}
350349

351-
T &operator*() { return Ptr = &*this->I; }
352-
const T &operator*() const { return Ptr = &*this->I; }
350+
T &operator*() const { return Ptr = &*this->I; }
353351
};
354352

355353
template <typename RangeT, typename WrappedIteratorT =

llvm/unittests/ADT/IteratorTest.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,90 @@ using IsAdaptedIterCategorySame =
5252
std::is_same<typename std::iterator_traits<It>::iterator_category,
5353
typename std::iterator_traits<A<It>>::iterator_category>;
5454

55+
// Check that dereferencing works correctly adapting pointers and proxies.
56+
template <class T>
57+
struct PointerWrapper : public iterator_adaptor_base<PointerWrapper<T>, T *> {
58+
PointerWrapper(T *I) : iterator_adaptor_base<PointerWrapper, T *>(I) {}
59+
};
60+
struct IntProxy {
61+
int &I;
62+
IntProxy(int &I) : I(I) {}
63+
void operator=(int NewValue) { I = NewValue; }
64+
};
65+
struct ConstIntProxy {
66+
const int &I;
67+
ConstIntProxy(const int &I) : I(I) {}
68+
};
69+
template <class T, class ProxyT>
70+
struct PointerProxyWrapper
71+
: public iterator_adaptor_base<PointerProxyWrapper<T, ProxyT>, T *,
72+
std::random_access_iterator_tag, T,
73+
ptrdiff_t, T *, ProxyT> {
74+
PointerProxyWrapper(T *I)
75+
: iterator_adaptor_base<PointerProxyWrapper, T *,
76+
std::random_access_iterator_tag, T, ptrdiff_t,
77+
T *, ProxyT>(I) {}
78+
};
79+
using IntIterator = PointerWrapper<int>;
80+
using ConstIntIterator = PointerWrapper<const int>;
81+
using IntProxyIterator = PointerProxyWrapper<int, IntProxy>;
82+
using ConstIntProxyIterator = PointerProxyWrapper<const int, ConstIntProxy>;
83+
84+
template <class T,
85+
std::enable_if_t<std::is_assignable<T, int>::value, bool> = false>
86+
constexpr bool canAssignFromInt(T &&) {
87+
return true;
88+
}
89+
template <class T,
90+
std::enable_if_t<!std::is_assignable<T, int>::value, bool> = false>
91+
constexpr bool canAssignFromInt(T &&) {
92+
return false;
93+
}
94+
95+
TEST(IteratorAdaptorTest, Dereference) {
96+
int Number = 1;
97+
98+
// Construct some iterators and check whether they can be assigned to.
99+
IntIterator I(&Number);
100+
const IntIterator IC(&Number);
101+
ConstIntIterator CI(&Number);
102+
const ConstIntIterator CIC(&Number);
103+
EXPECT_EQ(true, canAssignFromInt(*I)); // int *
104+
EXPECT_EQ(true, canAssignFromInt(*IC)); // int *const
105+
EXPECT_EQ(false, canAssignFromInt(*CI)); // const int *
106+
EXPECT_EQ(false, canAssignFromInt(*CIC)); // const int *const
107+
108+
// Prove that dereference and assignment work.
109+
EXPECT_EQ(1, *I);
110+
EXPECT_EQ(1, *IC);
111+
EXPECT_EQ(1, *CI);
112+
EXPECT_EQ(1, *CIC);
113+
*I = 2;
114+
EXPECT_EQ(2, Number);
115+
*IC = 3;
116+
EXPECT_EQ(3, Number);
117+
118+
// Construct some proxy iterators and check whether they can be assigned to.
119+
IntProxyIterator P(&Number);
120+
const IntProxyIterator PC(&Number);
121+
ConstIntProxyIterator CP(&Number);
122+
const ConstIntProxyIterator CPC(&Number);
123+
EXPECT_EQ(true, canAssignFromInt(*P)); // int *
124+
EXPECT_EQ(true, canAssignFromInt(*PC)); // int *const
125+
EXPECT_EQ(false, canAssignFromInt(*CP)); // const int *
126+
EXPECT_EQ(false, canAssignFromInt(*CPC)); // const int *const
127+
128+
// Prove that dereference and assignment work.
129+
EXPECT_EQ(3, (*P).I);
130+
EXPECT_EQ(3, (*PC).I);
131+
EXPECT_EQ(3, (*CP).I);
132+
EXPECT_EQ(3, (*CPC).I);
133+
*P = 4;
134+
EXPECT_EQ(4, Number);
135+
*PC = 5;
136+
EXPECT_EQ(5, Number);
137+
}
138+
55139
// pointeE_iterator
56140
static_assert(IsAdaptedIterCategorySame<pointee_iterator_defaulted,
57141
RandomAccessIter>::value, "");
@@ -178,6 +262,16 @@ TEST(FilterIteratorTest, Lambda) {
178262
EXPECT_EQ((SmallVector<int, 3>{1, 3, 5}), Actual);
179263
}
180264

265+
TEST(FilterIteratorTest, Enumerate) {
266+
auto IsOdd = [](auto N) { return N.value() % 2 == 1; };
267+
int A[] = {0, 1, 2, 3, 4, 5, 6};
268+
auto Enumerate = llvm::enumerate(A);
269+
SmallVector<int> Actual;
270+
for (auto IndexedValue : make_filter_range(Enumerate, IsOdd))
271+
Actual.push_back(IndexedValue.value());
272+
EXPECT_EQ((SmallVector<int, 3>{1, 3, 5}), Actual);
273+
}
274+
181275
TEST(FilterIteratorTest, CallableObject) {
182276
int Counter = 0;
183277
struct Callable {

0 commit comments

Comments
 (0)