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?

Posted 2018-08-29