How to join two views

283 views Asked by At

I'm trying to join two views:

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

using namespace std;

int main()
{
 
    vector v{1,2,3,4,5,6,7,4,8,9};   
    auto view_filtered = v | std::views::filter([](const int val){return val %2 == 0;});
      
    vector v_10{10,20,30,40,50,60,70,80,90};   
    auto view_transformed = v_10 | std::ranges::views::transform([](auto val){return val * 10;});
    auto join_views =view_transformed | std::ranges::views::join(view_filtered);
    


    return 0;
}

Can somebody tell me why is this not the correct way? Thank you

Edit:

main.cpp: In function ‘int main()’: main.cpp:24:65: error: no match for call to ‘(const std::ranges::views::_Join) (std::ranges::filter_view > >, main():: >&)’    24 |     auto join_views =view_transformed | std::ranges::views::join(view_filtered);
      |                                         ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~ In file included from main.cpp:9: /usr/include/c++/11/ranges:2785:9: note: candidate: ‘template  requires (viewable_range<_Range>) && (__can_join_view<_Range>) constexpr auto std::ranges::views::_Join::operator()(_Range&&) const’  2785 |         operator()(_Range&& __r) const
      |         ^~~~~~~~ /usr/include/c++/11/ranges:2785:9: note:   template argument deduction/substitution failed: /usr/include/c++/11/ranges:2785:9: note: constraints not satisfied /usr/include/c++/11/ranges: In substitution of ‘template<class _Range> requires (viewable_range<_Range>) && (__can_join_view<_Range>) constexpr auto std::ranges::views::_Join::operator()(_Range&&) const [with _Range = std::ranges::filter_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, main()::<lambda(int)> >&]’: main.cpp:24:65:   required from here /usr/include/c++/11/ranges:2776:10:   required for the satisfaction of ‘__can_join_view<_Range>’ [with _Range = std::ranges::filter_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, main::._anon_103>&] /usr/include/c++/11/ranges:2777:6:   in requirements  [with _Range = std::ranges::filter_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, main::._anon_103>&] /usr/include/c++/11/ranges:2777:24: note: the required expression ‘std::ranges::join_view >{declval<_Range>()}’ is invalid  2777 |      
= requires { join_view<all_t<_Range>>{std::declval<_Range>()}; };
      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ cc1plus: note: set ‘-fconcepts-diagnostics-depth=’ to at least 2 for more detail
4

There are 4 answers

1
alagner On

join is for joining elements of a range into a single one, e.g. characters into a string. What you're looking for is concatenation, unfortunately not available in C++ stdlib (yet at least).

range-v3 provides it auto join_views = ranges::views::concat(view_filtered, view_transformed); See: https://stackoverflow.com/a/62456584/4885321

Demo: https://godbolt.org/z/6T51T3b4f

4
Toby Speight On

join_view() takes a range of ranges.

If you can coerce your two ranges to the same type, you could make an array of them, and pipe that into std::views::join.

8
Caleth On

join takes a range of ranges, and returns all the elements of the inner ranges in one sequence. You can wrap your input ranges in std::generator to have something joinable.

template <std::ranges::range R>
std::generator<std::ranges::range_value_t<R>> as_generator(R && r) {
    co_yield std::elements_of(r);
}

Which gives you

auto join_views = std::ranges::views::join({ as_generator(view_transformed ), as_generator(view_filtered) });

But a simpler thing would be to write a concat yourself.

template <std::ranges::range... Rs>
std::generator<std::common_type_t<std::ranges::range_value_t<Rs>...>> concat(Rs... && rs) {
    co_yield std::elements_of(rs)...;
}

Which gives you

auto join_views = concat(view_transformed, view_filtered);
0
Pavel On

Using join isn't good way here. It’s better to use insert or copy – something like this:

std::vector result{ std::from_range, view_filtered };
result.insert(result.end(), begin(view_transformed), 
              end(view_transformed));

or more complex but more efficient in some cases:

std::vector<int> result;
result.reserve(view_filtered.size() + view_transformed.size())
result.insert(result.end(), begin(view_filtered), end(view_filtered));
result.insert(result.end(), begin(view_transformed), 
              end(view_transformed));

With join the code would be ugly:

auto result_view = std::vector{std::vector{std::from_range, view_filtered},
                               std::vector{std::from_range, view_transformed}} |                     
                   std::views::join