c++std-rangesc++23

Why is this std::view getting evaluated twice?


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?


Solution

  • std::ranges::to<std::vector>( output ) computes the length of output before initializing the elements. That is, there are two iterations:

    1. The first iteration is used to determine the length of output (which is also the size of the resulting std::vector).
    2. The second iteration is used to initialize the elements of the resulting std::vector.

    This approach is usually more efficient than a single iteration, because it avoids reallocation during vector construction.