1

I want to write a function like this:

template<class IterableType>
void CheckAndProcessIterables(IterableType& a, IterableType& b, IterableType& c) {
  IteratorRangeType range{}; // empty range
  if (Check(a)) {
    range = boost::range::join(range, a);
  }
  if (Check(b)) {
    range = boost::range::join(range, b);
  }
  if (Check(c)) {
    range = boost::range::join(range, c);
  }
  Process(range);
}

Is it possible? Which type should I use instead of IteratorRangeType? As far as I understand it, boost::range::join return type depends on it's arguments. Is there some wrapper class which can be assigned any type of range as long as its underlying value type is same?

sehe
  • 374,641
  • 47
  • 450
  • 633
Denis Sheremet
  • 2,453
  • 2
  • 18
  • 34

1 Answers1

1

You can use type erased iterator ranges, which Boost has in the form of any_range.

Beware of the performance cost of these, which can quickly become very noticable. I'd rethink the approach unless you're very sure that this not on any hot path and readability is a much more of a concern than performance.

Live On CoCompiler Explorer

#include <boost/range/join.hpp>
#include <boost/range/any_range.hpp>

// for demo only:
#include <boost/range/algorithm/for_each.hpp>
#include <boost/lambda/lambda.hpp>
#include <fmt/ranges.h>

template<class Range>
bool Check(Range const& r) {
    bool odd_len = boost::size(r) % 2;
    fmt::print("Check {}, odd_len? {}\n", r, odd_len);
    return odd_len;
}

template<class Range>
void Process(Range const& r) {
    fmt::print("Processing {}\n", r);
    using namespace boost::lambda;
    for_each(r, _1 *= _1);
}

template<class IterableType>
void CheckAndProcessIterables(IterableType& a, IterableType& b, IterableType& c) {
    using V = typename boost::range_value<IterableType>::type;
    using ErasedRange= boost::any_range<V, boost::forward_traversal_tag>;

    ErasedRange range{}; // empty range
    if (Check(a)) {
        range = boost::range::join(range, a);
    }
    if (Check(b)) {
        range = boost::range::join(range, b);
    }
    if (Check(c)) {
        range = boost::range::join(range, c);
    }
    Process(range);
}

int main() {
    std::vector a{1, 2, 3}, b{4, 5}, c{6, 7, 8};
    CheckAndProcessIterables(a, b, c);

    fmt::print("After process: a:{} b:{} c:{}\n", a, b, c);
}

Prints

Check {1, 2, 3}, odd_len? true
Check {4, 5}, odd_len? false
Check {6, 7, 8}, odd_len? true
Processing {1, 2, 3, 6, 7, 8}
After process: a:{1, 4, 9} b:{4, 5} c:{36, 49, 64}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Just for inspiration, this could be an alternative, still pretty generic, without any type erasure: https://godbolt.org/z/7xsxqPoqW – sehe May 01 '21 at 13:39
  • And as always(...) using std::ranges for the job seems a lot harder than it should. This took me half an hour to get working decently: https://godbolt.org/z/soobjYrrM On the bright side it keeps the "logical shape" of `Process` with a single range, and should not incure huge overhead (like with `any_range`). – sehe May 01 '21 at 14:09
  • I should probably be posting these versions as separate answers to gain marginally more rep from fringe questions like these :) – sehe May 01 '21 at 14:10
  • Thanks, that's exactly what I was looking for. Of course I will throughly consider the overhead cost before it comes anywhere near the prooduction environment, yet I feel comple knowing that thing exists the way I've imagined it. – Denis Sheremet May 01 '21 at 18:57