Walletfox.com

The purpose of std::views::common in C++20 Ranges


This situation applies only to C++20 Ranges and their mixing with older STL algorithms such as std::accumulate.

If you stick to views and range algorithms from Eric Niebler's and Casey Carter's Range-v3 library, you will not encounter the issue described below.

Let's look at two cases that differ only in their respective views. The first example uses std::views::filter, the second example uses std::views::take_while. Both are then processed using std::accumulate.

You can compile the examples at Wandbox using the latest GCC as a compiler.

The example with std::views::filter compiles just fine.

#include <iostream>
#include <numeric>
#include <vector>
#include <ranges>

// compiles fine
int main()  
{  
    auto v = std::vector{8,7,3};
    auto rng = v | std::views::filter([](int x){return x > 5 ;});
    auto res = std::accumulate(rng.begin(),rng.end(),0);
    std::cout << res << '\n'; // 15
} 

On the other hand, the example with std::views::take_while fails to compile.

#include <iostream>
#include <numeric>
#include <vector>
#include <ranges>

// fails to compile
int main()  
{  
    auto v = std::vector{8,7,3};
    auto rng = v | std::views::take_while([](int x){return x > 5 ;});
    auto res = std::accumulate(rng.begin(),rng.end(),0);
    std::cout << res << '\n'; // 15
} 

The reason for this is that unlike std::views::filter, std::views::take_while isn't a common_range. Common ranges require that ranges::begin and ranges::end return the same type.

At cppreference we read that filter_view models the concepts bidirectional_range, forward_range, input_range, and common_range. If we look at the description of take_while_view at cppreference, there is no mention of it being a common_range.

Therefore, to fix the problem for std::views::take_while, we have to add a conversion to std::views::common. The purpose of common_view is to take a C++20 range with a sentinel type different from its iterator type, and adapt it to work with C++17 algorithms, i.e. here std::accumulate, by producing the same iterator and sentinel type.

#include <iostream>
#include <numeric>
#include <vector>
#include <ranges>

// compiles fine
int main()  
{  
    auto v = std::vector{8,7,3};
    auto rng = v | std::views::take_while([](int x){return x > 5 ;})
                 | std::views::common;
    auto res = std::accumulate(rng.begin(),rng.end(),0);
    std::cout << res << '\n'; // 15
} 

Now, std::accumulate will process the range rng just fine.

Tagged: C++20 Ranges