boost::range::combine does not work with std::subrange

114 Views Asked by At

I try to apply boost::range::conbine to a couple of std::subranges but the example does not compile.

#include <array>
#include <ranges>
#include <boost/range/combine.hpp>
#include <iostream>

int main() {
  auto arr = std::array{1, 2, 3, 4, 5};

  auto firsts = std::ranges::subrange(arr.begin(), arr.end() - 1);  
  auto seconds = std::ranges::subrange(arr.begin() + 1, arr.end());  

  for (auto [f, s]: boost::range::combine(arr, arr)) {
    std::cout << f << " " << s << std::endl;
  }

  /*
  // Does not compile.
  for (auto [f, s]: boost::range::combine(firsts, seconds)) {
    std::cout << f << " " << s << std::endl;
  }
  */

  return 0;
}

What is the reason for boost not to support subrange? Or is it a bug?

1

There are 1 best solutions below

6
On BEST ANSWER

The subrange models a view, and in this particular case also sized_range. Neither of these satisfy combine's required Range Concept SinglePassRange.

The compiler tells you as much:

/home/sehe/custom/superboost/libs/range/include/boost/range/detail/combine_cxx11.hpp|25 col 59| error: no matching function for call to ‘begin(std::ranges::subrange<const int*, const int*, std::ranges::subrange_kind::sized>&)’

Using Boosts model instead

So you can use the Boost range model:

Live On Compiler Explorer

#include <array>
#include <boost/range/combine.hpp>
#include <fmt/ranges.h>
// #include <ranges>

int main() {
    static constexpr std::array arr{1, 2, 3, 4, 5};

    fmt::print("combine arrays: {}\n", boost::range::combine(arr, arr));

#if 1
    auto firsts  = boost::make_iterator_range(arr.begin(), arr.end() - 1);
    auto seconds = boost::make_iterator_range(arr.begin() + 1, arr.end());
#else
    auto firsts  = std::ranges::subrange(arr.begin(), arr.end() - 1);
    auto seconds = std::ranges::subrange(arr.begin() + 1, arr.end());
#endif

    fmt::print("combine ranges: {}\n", boost::range::combine(firsts, seconds));
}

Printing

combine arrays: [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
combine ranges: [(1, 2), (2, 3), (3, 4), (4, 5)]

Use std::views::zip instead

If you have a compiler that implements c++23 library supports already, you can use std::views::zip

Use RangeV3 library instead

This implementation contains things not (yet) standardized, and also supports zip

Extend Boost Range

You can make Boost recognize std::ranges::subrange. I think Method 2: Provide Free-standing Functions And Traits is probably appropriate. The absolute bare minimum allows it to work:

Live On Compiler Explorer

#include <array>
#include <boost/range/combine.hpp>
#include <fmt/ranges.h>
#include <ranges>

namespace boost {
    template <typename I, auto K> struct range_iterator<std::ranges::subrange<I, I, K>> {
        using type = I;
    };

    template <typename I, auto K> auto range_begin(std::ranges::subrange<I, I, K> const& r) {
        return r.begin();
    }
} // namespace boost

int main() {
    static constexpr std::array arr{1, 2, 3, 4, 5};

    fmt::print("combine arrays: {}\n", boost::range::combine(arr, arr));

    auto firsts  = std::ranges::subrange(arr.begin(), arr.end() - 1);
    auto seconds = std::ranges::subrange(arr.begin() + 1, arr.end());

    fmt::print("combine ranges: {}\n", boost::range::combine(firsts, seconds));
}

Printing

combine arrays: [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
combine ranges: [(1, 2), (2, 3), (3, 4), (4, 5)]

Summary/Notes

The above subtly assumes sized subranges (K is subrange_kind​::​sized), by requiring the sentinel is just an iterator. This is not very accurate (see e.g. sized_sentinel_for) but it was the simplest thing to do for the purpose of teaching Boost how to use the subranges from your question.