boostorg / range

Boost.org range module
http://boost.org/libs/range
43 stars 104 forks source link

SinglePassRangeConcept should support non-const range #133

Open grtowel1510f opened 2 years ago

grtowel1510f commented 2 years ago

A single pass range often can be consumed only once which implies mutability and negates constness, on the first consumption it becomes empty. SinglePassRangeConcept asserts const_constraints which is a requirement that cannot be met by many or most single pass ranges.

I will demonstrate using a boost coroutine with a range adaptor:

#include <boost/range/adaptor/transformed.hpp>
#include <boost/coroutine2/coroutine.hpp>

using boost::adaptors::transformed;
using boost::coroutines2::coroutine;

int main() {
    using generator = coroutine<int>;
    generator::pull_type g([](auto &yield) {
        for (int i = 0; i < 10; i++) {
            yield(i);
        }
    });

    for (auto i: g | transformed([](int i) { return i + 10; })) {
    }

    return 0;
}

This code snippet does not compile because SinglePassRangeConcept asserts const_constraints which boost coroutine does not satisfy.

I propose to drop the const requirement for SinglePassRangeConcept because in simple terms, a single pass range isn't required to have a const iterator.

grtowel1510f commented 2 years ago

This is a reduction of the previous coroutine demonstration using a simplified generator class to realize a better controlled experiment:

#include <iterator>
#include <boost/range/adaptor/transformed.hpp>

class generator {
public:
    int i = 0;
    int& value() { return i; }
    void next() { i++; }
    bool empty() const { return i == 10; }

    class iterator {
    public:
        using iterator_category = std::input_iterator_tag;
        using iterator_concept = std::input_iterator_tag;
        using difference_type = ptrdiff_t;
        using value_type = int;
        using pointer = int*;
        using reference = int&;

        generator *g;

        reference operator*() const { return g->value(); }
        iterator& operator++() {
            g->next();
            return *this;
        }
        inline iterator& operator++(int) { return operator++(); }
        bool operator==(const iterator& other) const {
            if (other.g == nullptr) {
                return !g || g->empty();
            }
            return g == other.g;
        }

        bool operator!=(const iterator& other) const { return !operator==(other); }

        iterator(generator& g) : g(&g) {}
        iterator() = default;
    };

    iterator begin() {
        return iterator(*this);
    }

    iterator end() {
        return iterator();
    }

};

using boost::adaptors::transformed;

int main() {
    generator g;
    for (auto i : g | transformed([](int i) { return i + 10; })) {
    }

    return 0;
}