Возможно ли использовать динамическое количество адаптеров диапазона?

#c #c 20 #std-ranges

#c #c 20 #стандартные диапазоны

Вопрос:

Я довольно новичок в диапазонах, и я хотел бы знать, есть ли способ применить динамическое количество адаптеров диапазона. Я некоторое время возился с некоторым кодом, а также провел некоторые поиски, но безрезультатно.

 #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";
}
 

По сути, я хочу что-то подобное, но это выдает ошибку компиляции ( no match for 'operator=' ). Кажется, что для каждого применения адаптера диапазона требуется новый тип, поэтому мы не можем динамически создавать этот диапазон. Есть ли какой-то способ обойти это?

Комментарии:

1. На самом деле было бы гораздо разумнее просто … фильтровать фактический контейнер. Зачем использовать отложенный просмотр, когда вы можете просто применить каждый фильтр по мере необходимости?

Ответ №1:

Для такого фиксированного числа можно было бы использовать метапрограммирование для рекурсивного построения диапазона (хотя вы можете столкнуться с ограничением глубины создания экземпляра шаблона). Вы можете создать действительно динамическое число, стирая диапазоны, чтобы цепочка фильтров была связана вызовами виртуальных функций. Результат медленный, а код болезненный, но это, безусловно, возможно.

Ответ №2:

Одной из альтернатив является сохранение результатов каждой фильтрации в a vector , что гарантирует, что тип диапазона после каждой операции является согласованным и может быть переназначен.

 #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";
}
 

ДЕМОНСТРАЦИЯ.

Однако это неэффективно и не является хорошим вариантом использования адаптеров диапазона. Поэтому вам, возможно, придется использовать более эффективные алгоритмы для реализации этого.

Ответ №3:

В этом конкретном случае вы можете вместо этого создать предикат фильтра:

 
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) amp;amp; (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";
}
 

Однако «Может» не означает «должен». Это довольно неэффективно, поскольку создает цепочку косвенных вызовов для предиката.