Извинение:
Я (частично) отвечаю на свой вопрос, потому что я думаю, что я узнал, что происходит здесь, механически, и потому что дополнительные детали не будутвписаться в комментарий.Я не уверен в этикете, так что, если это будет лучше, как редактирование вопроса - все еще остается открытым вопрос , почему библиотека разработана таким образом - пожалуйста, предложите, чтобы вкомментарии, которые я с радостью перенесу туда.
Фильтрация до нахождения конечного итератора
Я не очень подробно разбираюсь во внутренностях range-v3, поэтому яможет не иметь терминологии совершенно верно.Короче говоря, здесь нет противоречивого поведения.Когда вызов ranges::view::take
следует за вызовом ranges::view::filter
(или ranges::view::remove_if
), результирующий объект представления должен установить конечный итератор в какой-то момент во время итерации, чтобы выйти из цикла for.Если бы я подумал об этом, я бы вообразил, что цикл for на основе диапазона все еще расширяется до чего-то вроде
for (auto it = std::begin(perfects); it != std::end(perfects); ++it) {
...
}
(что, между прочим, ведет себя идентично в моих примерах) и что после того, как он нашелнеобходимое количество элементов в начале последующего вызова operator++
на it
требует специальной логики, чтобы сделать результат равным std::end(perfects)
, так что цикл завершается без выполнения какой-либо дополнительной работы.Но вместо этого, и это имеет некоторый смысл с точки зрения реализации, конечный итератор фактически соответствует следующему элементу, возвращаемому представлением filter
/ remove_if
.Предикат filter
продолжает зацикливаться на ranges::view::ints(1)
, пока не найдет тот, для которого предикат возвращает true
;предположительно, он становится конечным итератором, поскольку он не печатается в цикле for for ranged.
Простая демонстрация этого обеспечивается следующим кодом.Здесь есть два настраиваемых целых числа n
и m
, а функция предиката в filter
возвращает true для x <= n
, false
для n < x < n+m
и true
для x >= m
:
#include <iostream>
#include <range/v3/all.hpp>
using namespace std;
int main(int,char**) {
int n = 5;
int m = 3;
auto perfects = ranges::view::ints(1)
| ranges::view::filter([&n,&m] (int x) {
std::cout << "Checking " << x << "... ";
if (x <= n) {
return true;
} else if (x <= n + m) {
std::cout << std::endl;
return false;
}
return true;})
| ranges::view::take(n);
std::cout << "First " << n << " numbers:" << std::endl;
for (int z : perfects) {
std::cout << " take it!" << std::endl;
}
std::cout << "DONE." << std::endl;
}
Вы можете запустить этот код для различных значений n
и m
здесь: wandbox .По умолчанию вывод выглядит следующим образом:
First 5 numbers:
Checking 1... take it!
Checking 2... take it!
Checking 3... take it!
Checking 4... take it!
Checking 5... take it!
Checking 6...
Checking 7...
Checking 8...
Checking 9... DONE.
(я не переименовал переменную perfects
; очевидно, это уже не набор идеальных чисел).Даже после получения первых n
успехов, лямбда-предикат вызывается, пока не вернется true
.Поскольку целое число, которое возвращает true, 9, не печатается, это должен быть std::end(perfects)
, который нарушает цикл for.
Для меня остается загадкой, почему он это делает.Это не то, что я ожидал;это может привести к неожиданному поведению (например, если тело лямбда-функции не является чистым и изменяет захваченные объекты), и это может иметь большое влияние на производительность, как показано в исходном примере, который должен был бы выполнить примерно 10 ^ 15 операций по модулю, прежде чем достичьцелое число 33550336.