make_range
and reversed
make_range
and reversed
A simple force multiplier that should be in every C++11-and-later programmer’s toolbox:
template<class It>
struct iterator_range {
It begin_, end_;
constexpr It begin() const noexcept { return begin_; }
constexpr It end() const noexcept { return end_; }
};
template<class It>
constexpr auto make_range(It first, It last) noexcept {
return iterator_range<It>(first, last);
}
This lets us do
char *data = "hello world";
for (char elt : make_range(data+4, data+8)) {
putchar(elt);
}
(Yes, yes, CTAD
lets us say iterator_range
instead of make_range
if we don’t need portability to C++14.
Please raise your hand if that’s your situation.)
Also for your toolbox:
template<class Ctr>
constexpr auto reversed(Ctr&& c) noexcept {
return make_range(c.rbegin(), c.rend());
}
This lets us do
std::string data = "hello world";
for (char elt : reversed(data)) {
putchar(elt);
}
or by combining the two
char *data = "hello world";
for (char elt : reversed(make_range(data+4, data+8))) {
putchar(elt);
}
Adding convenience overloads of reversed(first, last)
, adding fallbacks
in terms of std::make_reverse_iterator(first)
, etc., are all
left as an exercise for the reader.
Discussion question: Why did I use a forwarding reference Ctr&&
above, instead of
either an lvalue reference Ctr&
or a const lvalue reference const Ctr&
? Why didn’t
I std::forward<Ctr>(c)
before calling .rbegin()
on it? Is there ever a case where you
might want to mark an object as an rvalue before getting an iterator into it? Why or why not?