Bailey Parker
Bailey Parker

Reputation: 15905

Creating a custom range class that wraps boost adaptors

I have a class that applies some boost transform adaptors to a range (for sake of example, in reality it's a lot more complex than this):

struct Foo {
    auto range() const {
        return boost::irange(0, 10)
            | boost::adaptors::transformed([] (auto x) { return x * 2; });
    }

    auto begin() const { return range().begin(); }
    auto end() const { return range().end(); }
};

This alone allows us to iterate over a Foo using a range for:

for (auto x : Foo()) {
    std::cout << num << std::endl;
}

However, this doesn't compose well with other boost adaptors or range operations (like boost::join):

auto bad = boost::join(boost::irange(0, 10), Foo());
auto also_bad = Foo() | boost::adaptors::transformed([] (auto x) { return x + 1; });

Both of the above provoke some nasty template errors. The former (bad):

In file included from test.cpp:4:
/usr/local/include/boost/range/join.hpp:30:70: error: no type named 'type' in
      'boost::range_iterator<const Foo, void>'
            BOOST_DEDUCED_TYPENAME range_iterator<SinglePassRange1>::type,
            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
/usr/local/include/boost/range/join.hpp:44:28: note: in instantiation of template class
      'boost::range_detail::joined_type<const Foo, const boost::integer_range<int> >' requested
      here
    : public range_detail::joined_type<SinglePassRange1, SinglePassRange2>::type
                           ^
test.cpp:34:16: note: in instantiation of template class 'boost::range::joined_range<const Foo,
      const boost::integer_range<int> >' requested here
    auto bad = boost::join(Foo(), range);
               ^

...

And the latter (also_bad):

In file included from test.cpp:1:
In file included from /usr/local/include/boost/range/any_range.hpp:17:
In file included from /usr/local/include/boost/range/detail/any_iterator.hpp:22:
In file included from /usr/local/include/boost/range/detail/any_iterator_wrapper.hpp:16:
In file included from /usr/local/include/boost/range/concepts.hpp:24:
/usr/local/include/boost/range/value_type.hpp:26:70: error: no type named 'type' in
      'boost::range_iterator<Foo, void>'
    struct range_value : iterator_value< typename range_iterator<T>::type >
                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
/usr/local/include/boost/range/adaptor/replaced.hpp:109:40: note: in instantiation of template
      class 'boost::range_value<Foo>' requested here
                BOOST_DEDUCED_TYPENAME range_value<SinglePassRange>::type>& f )
                                       ^
test.cpp:35:27: note: while substituting deduced template arguments into function template
      'operator|' [with SinglePassRange = Foo]
    auto also_bad = Foo() | boost::adaptors::transformed([] (auto x) { return x * 2; });
                          ^
...

Both of the errors seem to be complaining that Foo isn't a range. I've tried adding an operator OutContainer() and typedefs for iterator/const_iterator as suggested here to no avail. What must I do to Foo to allow it to play nicely with these range operations?

Upvotes: 0

Views: 648

Answers (1)

Barry
Barry

Reputation: 303067

The error is super helpful in this case. Your type has to model SinglePassRange and it does not. There's a page about how to do this, and while you provided begin() and end(), you did not provide type aliases for iterator and const_iterator. Hence, you don't model SinglePassRange.

But it's actually quite good that your code failed, because it is also bad. You have begin() call range().begin() and end() call range().end(). Those are iterators into different ranges. So this is undefined behavior anyway - you're failing to meet the semantic concepts of a SinglePassRange.

The easier solution is to just use Foo() directly. This already works:

auto good = boost::join(boost::irange(0, 10), Foo().range());
auto also_good = Foo().range() | boost::adaptors::transformed([] (auto x) { return x + 1; });

And means you just have to write one function: range()

Upvotes: 3

Related Questions