6

I start to play with std::ranges and want understand how views really work. So I try to write my own container and iterator type and want to use it in a view.

But something seems to be missing but the compiler only tells me that there is no begin() method inside the view but not why.

Example:

#include <iostream>
#include <array>
#include <ranges>

class MyFixedContainer;
class MyIterator
{
    MyFixedContainer* ptr;
    unsigned int offset;
public:
    MyIterator( MyFixedContainer* ptr_, unsigned int offset_ ): ptr{ ptr_},offset{offset_}{}

    bool operator==( MyIterator& other ) const
    {   
        return ( ptr == other.ptr )&& ( offset == other.offset );
    }   

    bool operator!=( MyIterator& other ) const
    {   
        return !(*this == other);
    }   

    MyIterator operator++()
    {   
        offset++;
        return *this;
    }   

    MyIterator operator++(int)
    {   
        MyIterator tmp = *this;
        offset++;
        return tmp;
    }   

    int operator*() const;
};

class MyFixedContainer
{
    std::array<int,4> arr={5,6,7,8};
public:
    auto begin() { return MyIterator{ this, 0 }; }
    auto end() { return MyIterator{ this, 4}; }

    int Get( int offset ) const
    {
        return arr[ offset ];
    }
};

int MyIterator::operator*() const
{
    return ptr->Get( offset );
}

int main()
{
    MyFixedContainer c;

    // Container type itself works:
    for ( int i: c )
    {
        std::cout << i << std::endl;
    }

    // Try to use with std::ranges
    auto even = [] (int i) { return 0 == i % 2; };

    auto y = std::views::filter(c, even);
    auto b = y.begin(); // << error message
}

Compiles with

main.cpp:90:16: error: 'struct std::ranges::views::__adaptor::_RangeAdaptorClosurestd::ranges::views::__adaptor::_RangeAdaptor<_Callable::operator()<{MyFixedContainer&, main()::<lambda(int)>&}>::<lambda(_Range&&)> >' has no member named 'begin' 90 | auto b = y.begin();

https://godbolt.org/z/doW76j

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • By compile your code (after add includes), the first message I get is `'MyFixedContainer' does not name a type` . https://wandbox.org/permlink/XUwpnVHerzhb9q1R – apple apple Sep 08 '20 at 08:41
  • @ Klaus, I know, I just point out it would be better if we can simply copy&past&compile and get the same error. – apple apple Sep 08 '20 at 08:47
  • Basically a dupe of my previous [question](https://stackoverflow.com/questions/63549185/what-are-the-rules-for-creating-your-own-pipeable-ranges-views-and-actions). – Fureeish Sep 10 '20 at 12:29

1 Answers1

8

MyIterator does not model std::input_or_output_iterator because:

  • It needs to be default constructible.
  • std::iter_difference_t<MyIterator> must be valid, and
  • the pre-increment operator must return a reference.

MyIterator is not a std::sentinel_for<MyIterator, MyIterator> because its operators == and != take references instead of const references.

MyIterator does not satisfy std::input_iterator, which requires std::iter_value_t to be valid.

Fixing all of the above:

#include <iostream>
#include <array>
#include <ranges>

class MyFixedContainer;
class MyIterator
{
    MyFixedContainer* ptr;
    unsigned int offset;
public:
    using difference_type = int;
    using value_type = int;
    
    MyIterator() = default;
    
    MyIterator( MyFixedContainer* ptr_, unsigned int offset_ ): ptr{ ptr_},offset{offset_}{}

    bool operator==( MyIterator const & other ) const
    {   
        return ( ptr == other.ptr )&& ( offset == other.offset );
    }   

    bool operator!=( MyIterator const & other ) const
    {   
        return !(*this == other);
    }   

    MyIterator &operator++()
    {   
        offset++;
        return *this;
    }   

    MyIterator operator++(int)
    {   
        MyIterator tmp = *this;
        offset++;
        return tmp;
    }   

    int operator*() const;
};

class MyFixedContainer
{
    std::array<int,4> arr={5,6,7,8};
public:
    auto begin() { return MyIterator{ this, 0 }; }
    auto end()   { return MyIterator{ this, 4}; }

    int Get( int offset ) const
    {
        return arr[ offset ];
    }
};

int MyIterator::operator*() const
{
    return ptr->Get( offset );
}

int main()
{
    MyFixedContainer c;

    // Container type itself works:
    for ( int i: c )
    {
        std::cout << i << std::endl;
    }

    // Try to use with std::ranges
    auto even = [] (int i) { return 0 == i % 2; };

    static_assert(std::input_or_output_iterator<MyIterator>);
    static_assert(std::ranges::input_range<MyFixedContainer>);
    
    auto y = c | std::views::filter(even);
    
    auto b = y.begin(); // << OK
}

The error messages are much clearer if you static_assert every concept that your container/iterator has to model.

metalfox
  • 6,301
  • 1
  • 21
  • 43
  • "The error messages are much clearer if you static_assert"... But there my problem starts: Which concept must be fulfilled...? Now I got the answer from you and can handle it. Thanks very much! – Klaus Sep 08 '20 at 12:50
  • 1
    @Klaus Oh it’s not that hard at all. Say that you want your container to be a `std::ranges::random_access_range`. You only need to have a look at what concepts it subsumes. Thanks to concepts (and the hard work of C. Carter and E. Niebler, among others), now the algorithms library is self-documenting. That’s a significant improvement over the previous status quo. – metalfox Sep 09 '20 at 05:51