4

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?

Jack Casey
  • 65
  • 1
  • 4
  • 1
    It would really make a lot more sense to just... filter the actual container. Why use a lazy view when you could just apply each filter as needed? – Nicol Bolas Dec 04 '21 at 04:38

3 Answers3

6

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.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
4

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.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
1

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.

bolov
  • 72,283
  • 15
  • 145
  • 224