I have the following test program:
#include <vector>
#include <print>
#include <ranges>
int main() {
const std::vector<int> input = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto output = input
| std::views::filter([](const int n) { std::print("{} ", n); return n % 3 == 0; })
| std::views::transform([](const int n) {return n * n; });
const std::vector<int> vector = std::ranges::to<std::vector>( output );
std::println("\ninput size: {} output size: {}", input.size(), vector.size() );
}
which, when compiled in Xcode 16.2 with c++23 mode, outputs this:
0 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10
input size: 11 output size: 4
I'm confused by this. Why is each item evaluated twice when being converted to std::vector?
When I try a simple for
cycle to print the elements, each item is only evaluated once:
#include <vector>
#include <print>
#include <ranges>
int main() {
const std::vector<int> input = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto output = input
| std::views::filter([](const int n) { std::print("{} ", n); return n % 3 == 0; })
| std::views::transform([](const int n) {return n * n; });
int sum = 0;
for ( int i : output ) sum += i;
std::println("\nsum: {}", sum);
}
outputs:
0 1 2 3 4 5 6 7 8 9 10
sum: 126
The first program seems inefficient and makes me reconsider if I should start using ranges... is there a better way to write it?
std::ranges::to<std::vector>( output )
computes the length of output
before initializing the elements. That is, there are two iterations:
output
(which is also the size of the resulting std::vector
).std::vector
.This approach is usually more efficient than a single iteration, because it avoids reallocation during vector construction.