Iterating and inverting a const views::filter
views::filter
Via Alexej Harm on Slack, two little hacks for dealing with std::ranges::filter_view
.
First, recall that filter_view
isn’t const-iterable — because its iterators keep pointers into
the view itself, and modify it as you iterate. So if you are
“constexpr’ing all the things”
(not that I recommend that), how do you iterate a const filter_view
?
constexpr auto isPrint = [](auto c) { return std::isprint(c); };
constexpr auto isXDigit = [](auto c) { return std::isxdigit(c); };
constexpr auto hexdigital = std::views::filter(isXDigit);
constexpr auto printable = std::views::filter(isPrint);
constexpr auto digits = std::views::iota('\0')
| std::views::take(256)
| hexdigital;
for (char c : digits) ~~~ // Error
for (char c : digits | printable) ~~~ // Error
C++23 to the rescue!
for (char c : auto(digits)) ~~~ // OK
for (char c : auto(digits) | printable) ~~~ // OK
Here we aren’t trying to iterate over the const-qualified digits
: we’re asking the compiler
to make us a copy (just like int(i)
makes a prvalue copy of i
), and then iterating over
that copy.
Second, given an existing filter_view
, can we “invert its sense”? For example, given digits
as above, can we easily iterate over the non-digits? It turns out that C++20 filter_view
exposes enough public getters to make this possible!
constexpr auto rest = [](auto fv) {
return fv.base() | std::views::filter(std::not_fn(fv.pred()));
};
for (char c : rest(digits)) ~~~ // OK
for (char c : rest(digits) | printable) ~~~ // OK
Notice that we’re using fv.base()
on the filter_view
itself, to extract the base view.
We might have written ranges::subrange(fv.begin().base(), fv.end().base())
instead,
but that would cause dangling-pointer bugs
if those iterators referred back into something captured inside fv
. We should never do that.
Never prematurely “debone” a range type by splitting it into
begin
andend
. Do that only when you’re about to traverse it, if ever at all.
For greater applicability to move-only views such as owning_view
,
we might consider calling std::move(fv).base()
instead of fv.base()
,
or taking auto&& fv
and calling decltype(fv)(fv).base()
.
As always, there’s a tradeoff between generality and maintainability.