This is an archive of the discontinued LLVM Phabricator instance.

Fix iterator_adaptor_base/enumerator_iter to allow composition of llvm::enumerate with llvm::make_filter_range
ClosedPublic

Authored by mehdi_amini on Nov 1 2021, 9:53 PM.

Diff Detail

Event Timeline

mehdi_amini created this revision.Nov 1 2021, 9:53 PM
mehdi_amini requested review of this revision.Nov 1 2021, 9:53 PM
Herald added a project: Restricted Project. · View Herald TranscriptNov 1 2021, 9:53 PM
mehdi_amini added inline comments.
llvm/unittests/ADT/IteratorTest.cpp
186

(This is to fail to compile)

186

(I meant: this used to fail to compile)

rriddle accepted this revision.Nov 1 2021, 9:59 PM

Can you update the description with information on what this is fixing?

This revision is now accepted and ready to land.Nov 1 2021, 9:59 PM

Can you update the description with information on what this is fixing?

I am not sure how to phrase this, the first error was:

/llvm/include/llvm/ADT/iterator.h:299:41: error: binding reference of type 'result_pair<...>' to value of type 'const result_pair<...>' drops 'const' qualifier
  ReferenceT operator*() const { return *I; }
                                        ^~
llvm/unittests/ADT/IteratorTest.cpp:186:26: note: in instantiation of member function 'llvm::iterator_adaptor_base<llvm::filter_iterator_base<llvm::detail::enumerator_iter<int (&)[7]>, (lambda at llvm/unittests/ADT/IteratorTest.cpp:182:16), std::forward_iterator_tag>, llvm::detail::enumerator_iter<int (&)[7]>, std::forward_iterator_tag, llvm::detail::result_pair<int (&)[7]>, long, int *, llvm::detail::result_pair<int (&)[7]> &>::operator*' requested here
  for (auto IndexedValue : make_filter_range(Enumerate, IsOdd))
                         ^

The fix in STLExtra is for:

llvm/unittests/ADT/IteratorTest.cpp:187:34: error: member reference base type 'int' is not a structure or union
    Actual.push_back(IndexedValue.value());
                     ~~~~~~~~~~~~^~~~~~
In file included from llvm/unittests/ADT/IteratorTest.cpp:10:
llvm/include/llvm/ADT/iterator.h:300:35: error: non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'llvm::detail::enumerator_iter<int (&)[7]>::result_type' (aka 'result_pair<int (&)[7]>')
  ReferenceT operator*() { return *I; }
                                  ^~
llvm/unittests/ADT/IteratorTest.cpp:186:26: note: in instantiation of member function 'llvm::iterator_adaptor_base<llvm::filter_iterator_base<llvm::detail::enumerator_iter<int (&)[7]>, (lambda at llvm/unittests/ADT/IteratorTest.cpp:182:16), std::forward_iterator_tag>, llvm::detail::enumerator_iter<int (&)[7]>, std::forward_iterator_tag, llvm::detail::result_pair<int (&)[7]>, long, int *, int &>::operator*' requested here
  for (auto IndexedValue : make_filter_range(Enumerate, IsOdd))
                         ^

Can you update the description with information on what this is fixing?

I am not sure how to phrase this, the first error was:

/llvm/include/llvm/ADT/iterator.h:299:41: error: binding reference of type 'result_pair<...>' to value of type 'const result_pair<...>' drops 'const' qualifier
  ReferenceT operator*() const { return *I; }
                                        ^~
llvm/unittests/ADT/IteratorTest.cpp:186:26: note: in instantiation of member function 'llvm::iterator_adaptor_base<llvm::filter_iterator_base<llvm::detail::enumerator_iter<int (&)[7]>, (lambda at llvm/unittests/ADT/IteratorTest.cpp:182:16), std::forward_iterator_tag>, llvm::detail::enumerator_iter<int (&)[7]>, std::forward_iterator_tag, llvm::detail::result_pair<int (&)[7]>, long, int *, llvm::detail::result_pair<int (&)[7]> &>::operator*' requested here
  for (auto IndexedValue : make_filter_range(Enumerate, IsOdd))
                         ^

The fix in STLExtra is for:

llvm/unittests/ADT/IteratorTest.cpp:187:34: error: member reference base type 'int' is not a structure or union
    Actual.push_back(IndexedValue.value());
                     ~~~~~~~~~~~~^~~~~~
In file included from llvm/unittests/ADT/IteratorTest.cpp:10:
llvm/include/llvm/ADT/iterator.h:300:35: error: non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'llvm::detail::enumerator_iter<int (&)[7]>::result_type' (aka 'result_pair<int (&)[7]>')
  ReferenceT operator*() { return *I; }
                                  ^~
llvm/unittests/ADT/IteratorTest.cpp:186:26: note: in instantiation of member function 'llvm::iterator_adaptor_base<llvm::filter_iterator_base<llvm::detail::enumerator_iter<int (&)[7]>, (lambda at llvm/unittests/ADT/IteratorTest.cpp:182:16), std::forward_iterator_tag>, llvm::detail::enumerator_iter<int (&)[7]>, std::forward_iterator_tag, llvm::detail::result_pair<int (&)[7]>, long, int *, int &>::operator*' requested here
  for (auto IndexedValue : make_filter_range(Enumerate, IsOdd))
                         ^

It was more of a passing comment, but something as simple as:

* Properly specify reference type in enumerator_iter
* Fix constness of iterator_adaptor_base::operator*

This doesn't look correct to me.

Here are two examples that I think this patch breaks:

// Setup: simple wrappers around `int *` and `const int *`.
struct const_iterator : public iterator_adaptor_base<const_iterator, const int *> {};
struct iterator : public iterator_adaptor_base<iterator, int *> {};

// `const`-qualified iterators (wrapping `int *const` and `const int *const`).
const int &ConstRef = *std::declval<const const_iterator &>(); // (example #1)
int &Ref = *std::declval<const iterator &>(); // (example #2)

The const-qualification on iterator_adaptor_base should not affect the reference value:

  • non-const iterator: int * (dereferencing returns int &)
  • non-const iterator with const: int *const (dereferencing returns int &)
  • const iterator: const int * (dereferencing returns const int &)
  • const iterator with const: const int *const (dereferencing returns const int &)

I think I found the actual bug; see my inline comments.

llvm/include/llvm/ADT/STLExtras.h
1920–1921

This is the actual bug. The const-ness of the iterator shouldn't affect the const-ness of the returned value.

The non-const operator*() should be deleted and only a const result_type& should ever be returned. The caller should never be allowed to modify Result.Iter or Result.Index.

llvm/include/llvm/ADT/iterator.h
299–300

If you used std::add_const_t<ReferenceT> then I think (1) would start compiling again but (2) would still fail.

I think this code change would "fix" the problem you encountered:

ReferenceT operator*() const { return *const_cast<WrappedIteratorT &>(I); }

... but I think the actual bug is in the WrappedIteratorT.

Here are two examples that I think this patch breaks:

const int &ConstRef = *std::declval<const const_iterator &>(); // (example #1)
int &Ref = *std::declval<const iterator &>(); // (example #2)

I was wrong that these would fail, but the reason seems a bit subtle: it appears that const ReferenceT is the same as int& when ReferenceT is int&.

I didn't see the revert here, and wrote https://reviews.llvm.org/D113158 to go on top. There were a few other problems to work through. Happy to rebase that if it makes sense.

Ah if you can rebase then that'd be great :)