0

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?

Barry
  • 286,269
  • 29
  • 621
  • 977
Bailey Parker
  • 15,599
  • 5
  • 53
  • 91
  • 2
    If you look at [the docs](https://www.boost.org/doc/libs/release/libs/range/doc/html/range/reference/utilities/join.html), it says that the range must model [*SinglePassRange*](https://www.boost.org/doc/libs/release/libs/range/doc/html/range/concepts/single_pass_range.html) (it helpfully gives a link). This concept description tells you everything your type needs. Also, see [*Extending the library*](https://www.boost.org/doc/libs/1_68_0/libs/range/doc/html/range/reference/extending.html) – Justin Oct 17 '18 at 21:49

1 Answers1

3

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()

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks! I know this question sounded like I lazily hadn't read any docs. I had read that page, but didn't understand what from it was relevant to my issue. Providing a `range()` method was much simpler. Although, if I had chosen to go the route of making `Foo` range-able (if that's the right terminology). What would I have needed to return from `begin()` and `end()`? Why are `range().begin()` and `range().end()` iterators into different ranges? Would `auto r = range()` then `r.begin()` and `r.end()` have been more correct? – Bailey Parker Oct 27 '18 at 23:48
  • Further (sorry to pile on), if this is the case, why aren't ranges like iterators (where calls to `begin()` and `end()` are always compatible)? And although `SinglePassRange` is only defined with respect to `SinglePassIterator` (where the docs for this give only opsem), the following is still unclear to me: is it just named poorly, or would I only be able to iterate over a type modeling this once? It seems like the `begin()` iterator being copy constructible would allow me to keep it around (and then always use a new begin from which to start iterating). – Bailey Parker Oct 28 '18 at 00:00