gdbsupport: make filtered_iterator work with pointers

It's currently not possible to use filtered_iterator with a pointer as
the base iterator type.  This patch makes it possible.  The indended
usage is:

  Foo array[12];
  Foo *begin = array;
  Foo *end = array + ARRAY_SIZE (array);
  filtered_iterator<Foo *, FooFilter> (begin, end);

Here are the things that needed changing:

 - Give filtered_iterator a constructor where the caller provides
   already constructed begin and end iterators.  filtered_iterator
   currently assumes that default-constructing a BaseIterator will
   produce a valid "end" iterator.  This is not the case if BaseIterator
   is a pointer.  The caller needs to pass in the end of the array /
   region to iterate on as the end.

 - Typedefs of member types like wouldn't work:

     typedef typename BaseIterator::value_type value_type;

   The compiler would complain that it's not possible to apply `::` to
   type `BaseIterator` (aka `Foo *`).  Use std::iterator_traits to fix
   it [1].

 - Similarly, the compiler would complain about the use of
   `BaseIterator::operator*` in the return type of
   `filtered_iterator::operator*`.  Fix this by using `decltype(auto)`
   as the return type.  This lets the compiler deduce the return type
   from the return statement.  Unlike `auto`, `decltype(auto)` perfectly
   preserves the "cvref-ness" of the deduced return type.  If the return
   expression yields a `Foo &`, then the function will return a `Foo &`
   (which is what we want), whereas it would return a `Foo` if we used
   just `auto`.

Improve the filtered_iterator unit tests to run the same tests but with
pointers as iterators.  Because the filtered_iterator objects are
initialized differently in the two scenarios, I chose to copy the
existing code and adapt it.  It would probably be possible to add a
layer of abstraction to avoid code duplication, but it would end up more
complicated and messy.  If we ever add a third scenario, we can revisit
that.

[1] https://en.cppreference.com/w/cpp/iterator/iterator_traits.html

Change-Id: Id962ffbcd960a705a82bc5eb4808b4fe118a2761
Approved-By: Tom Tromey <tom@tromey.com>
This commit is contained in:
Simon Marchi
2025-08-28 11:10:50 -04:00
committed by Simon Marchi
parent 7a8821ff0e
commit 1e10e9ea96
2 changed files with 65 additions and 11 deletions

View File

@@ -19,8 +19,6 @@
#ifndef GDBSUPPORT_FILTERED_ITERATOR_H
#define GDBSUPPORT_FILTERED_ITERATOR_H
#include <type_traits>
/* A filtered iterator. This wraps BaseIterator and automatically
skips elements that FilterFunc filters out. Requires that
default-constructing a BaseIterator creates a valid one-past-end
@@ -30,12 +28,14 @@ template<typename BaseIterator, typename FilterFunc>
class filtered_iterator
{
public:
typedef filtered_iterator self_type;
typedef typename BaseIterator::value_type value_type;
typedef typename BaseIterator::reference reference;
typedef typename BaseIterator::pointer pointer;
typedef typename BaseIterator::iterator_category iterator_category;
typedef typename BaseIterator::difference_type difference_type;
using self_type = filtered_iterator;
using value_type = typename std::iterator_traits<BaseIterator>::value_type;
using reference = typename std::iterator_traits<BaseIterator>::reference;
using pointer = typename std::iterator_traits<BaseIterator>::pointer;
using iterator_category
= typename std::iterator_traits<BaseIterator>::iterator_category;
using difference_type
= typename std::iterator_traits<BaseIterator>::difference_type;
/* Construct by forwarding all arguments to the underlying
iterator. */
@@ -44,6 +44,10 @@ public:
: m_it (std::forward<Args> (args)...)
{ skip_filtered (); }
filtered_iterator (BaseIterator begin, BaseIterator end)
: m_it (std::move (begin)), m_end (std::move (end))
{ skip_filtered (); }
/* Create a one-past-end iterator. */
filtered_iterator () = default;
@@ -56,9 +60,7 @@ public:
: filtered_iterator (static_cast<const filtered_iterator &> (other))
{}
typename std::invoke_result<decltype(&BaseIterator::operator*),
BaseIterator>::type
operator* () const
decltype(auto) operator* () const
{ return *m_it; }
self_type &operator++ ()