Is it possible to use a dynamic number of range adaptors?

Viewed 100

I am fairly new to ranges, and I wanted to know if there was a way to apply a dynamic number of range adaptors. I have fiddled around with some code for a while, and I have also done some searching, but to no avail.

#include <iostream>
#include <ranges>

int main() {
    auto output = std::ranges::views::iota(2, 100);

    for (int i = 2; i < 100; i++) {
        output = output | std::ranges::views::filter([i](int num){ return num % i != 0 || num == i; });
    }

    std::cout << "The 10th prime is: " << output[9] << "\n";
}

Essentially, I want something like this, but this gives a compile error (no match for 'operator='). It seems that each application of a range adaptor requires a new type, so we can't dynamically create this range. Is there some way around this?

3 Answers

For a fixed number like this, it would be possible to use metaprogramming to recursively build the range (although you might hit a template instantiation depth limit). You can do a truly dynamic number by type-erasing the ranges, such that the chain of filters is connected by virtual function calls. The result is slow and the code is painful, but it’s certainly possible.

One of the alternatives is to store the results of each filtering in a vector, which ensures that the range type after each operation is consistent and can be re-assigned.

#include <iostream>
#include <ranges>
#include <vector>

auto to_vector(std::ranges::view auto view) {
  return std::vector(view.begin(), view.end());
}

int main() {
  auto output = to_vector(std::views::iota(2, 100));

  for (int i = 2; i < 100; i++) {
    output = to_vector(output | std::views::filter(
                          [i](int num){ return num % i != 0 || num == i; }));
  }

  std::cout << "The 10th prime is: " << output[9] << "\n";
}

Demo.

However, this is inefficient and not a good use case for using range adaptors. So you may need to use more efficient algorithms to implement this.

In this particular case you can build the filter predicate instead:


int main() {
    auto output = std::views::iota(2, 100);

    std::function<bool(int)> filter_fn = [] (int) { return true; };

    for (int i = 2; i < 100; i++)
    {
        filter_fn = [=] (int num) {
            return filter_fn(num) && (num % i != 0 || num == i);
        };
    }

    auto primes = output | std::views::filter(filter_fn);

    std::cout << "The 10th prime is: " <<
        (primes | std::views::drop(9)).front() << "\n";
}

Can doesn't mean should though. This is pretty inefficient as it creates a chain of indirect calls for the predicate.

Related