Repository: ericniebler/range-v3

Issue #385: `const`-ness of view operations
Created: 2016-05-31T22:46:32Z
Containers
Containers in C++ may have both const and non-const overloads of begin. They differ only in their return types: non-const begin returns a mutable iterator, and const begin a constant iterator. Neither overload changes the bits of the container representation or modifies the semantic value of the container object. Two overloads exist so that it is necessary to establish a non-const access path to the container object in order to obtain a mutable iterator. end is the same. size is always const because it provides no way to modify the container's content.
Views
Views in range-v3 may have both const and non-const overloads of begin/end/size (herein termed "operations"). Views have pointer semantics - a view is essentially a pointer to a sequence of elements - so mutability of the elements viewed is orthogonal to mutability of the view object itself. The const distinction here has no relation to that of containers. Non-const operations do not modify the semantic objects being viewed, nor do they "swing the pointer" so that the same view designates different semantic objects. Non-const operations mutate internal state that does not contribute to the semantic value of the view; the const-ness here is purely bitwise.
The const-ness model used by views makes view composition painful. You can always provide the non-const overloads, but const overloads are preferred when achievable. So a composer, e.g.:
template<typename View>
class wrap_view : view_facade<wrap_view<View>>
{
    View base_;
public:
    wrap_view() = default;
    wrap_view(View v)
    : base_(std::move(v))
    {}
    // ...
};
ends up providing two definitions of each operation: one const that's constrained to require const operations over the underlying view(s):
    // We need the dependent type V so that range_iterator_t<V const> doesn't hard
    // error when View has no const begin operation (or: use C++14 deduced return type).
    template <typename V = View, CONCEPT_REQUIRES_(Range<View const>())>
    range_iterator_t<V const> begin() const
    { return ranges::begin(base_); }
and one mutable that's constrained to only be available when the const version isn't:
    CONCEPT_REQUIRES(!Range<View const>())
    range_iterator_t<View> begin()
    { return ranges::begin(base_); }
Ranges
I'm concerned that the differing const distinctions for containers and views don't mesh well into a consistent notion of what const means for operations on general ranges. I see a potential for latent bugs where a programmer accustomed to the fact that calling begin/end on mutable containers is threadsafe calls begin/end on mutable ranges without realizing there are sharp corners here.
The only mutating operation on pointers is assignment. If views are supposed to be range-pointers, perhaps assignment should be the only mutating operation? We (I) need to investigate an alternative model where view operations are always const and perform internal synchronization if needed.

Duplicate issue #428: problems with custom views, const and remove_if
Created: 2016-07-06T17:12:07Z
I've built a simple custom view with view_facade.
Whenever I pipe its ouput to view::remove_if and bind the result to a const reference, I become unable to call .begin() on it (it works fine with a non-const rvalue reference)
It works fine if remove_if is replaced by another adaptor like view::transform.
I tried to look at what happens, but it's a complex dance between begin and range_access::begin_cursor.
Minimal reproducer
#include <range/v3/all.hpp>

struct my_range : ranges::view_facade<my_range>
{
private:
    friend ranges::range_access;

    struct cursor
    {
        int get() const
        {
            return 0;
        }

        bool equal(cursor const&) const
        {
            return true;
        }

        void next()
        {
        }
    };

    cursor begin_cursor() const { return cursor(); }
    cursor end_cursor()   const { return cursor(); }
};

int main()
{
    auto const& rng = my_range() | ranges::view::remove_if([](auto&&) { return false; });
    auto it = rng.begin();
}
I might be doing things wrong, but in any case the error message is not helpful at all.

Closing comment (2016-07-09T22:19:11Z): Duplicate of #385.