Klaus
Klaus

Reputation: 25643

requirements for custom container type to use with views

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

Upvotes: 8

Views: 1029

Answers (1)

metalfox
metalfox

Reputation: 6751

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.

Upvotes: 9

Related Questions