jjcasmar
jjcasmar

Reputation: 1675

understanding how zip in range-v3 works

I am trying to understand how ranges::views::zip works in range-v3. I understand that it is a range that allows to iterate on several ranges in one single loop by creating a tuple of the elements in different ranges.

std::vector<int> v1 = {0, 1, 2};
std::vector<char> v2 = {'a', 'b', 'c'};


auto zip = ranges::views::zip(v1,v2);
// zip(v1,v2) = [(0,a), (1,b), (2,c)]

ranges::actions::sort(zip);
std::sort(std::begin(zip), std::end(zip));

The sort using ranges::actions works fine but std::sort doesnt compile and gives the following error

/usr/include/c++/9.3.0/bits/stl_algobase.h:151: error: no matching function for call to ‘swap(concepts::return_t<ranges::common_pair<int&, double&>, void>, concepts::return_t<ranges::common_pair<int&, double&>, void>)’
  151 |       swap(*__a, *__b);
      |       ~~~~^~~~~~~~~~~~

Why is this happening?

I have also tried to remove elements in both containers at the same time. ranges::actions::unique doesn't compile with the following error:

/home/jjcasmar/projects/cpfsofaplugin/src/CPFSofaPlugin/minimalExample.cpp:27: error: no match for call to ‘(const ranges::actions::action_closure<ranges::actions::unique_fn>) (ranges::zip_view<ranges::ref_view<std::vector<int, std::allocator<int> > >, ranges::ref_view<std::vector<double, std::allocator<double> > > >&)’
   27 |     ranges::actions::unique(v1Andv2);
      |                                    ^

but auto lastIt = std::unique(std::begin(v1Andv2), std::end(v1Andv2)) compiles find, although I dont know how to get the internal iterators of the zip to be able to erase past the end elements.

I dont really understand how this works under the hood and why in some cases std algorithms work fine but in some cases it doesn't. Can someone give some explanation about this?

Upvotes: 0

Views: 4724

Answers (2)

Porsche9II
Porsche9II

Reputation: 661

You cannot use std::sort on views. But you can transform your view to a vector and then it works: https://godbolt.org/z/_FvCdD

I can recommend the following sites for more information on ranges:

https://www.walletfox.com/course/quickref_range_v3.php https://mariusbancila.ro/blog/2019/01/20/cpp-code-samples-before-and-after-ranges/

Upvotes: 0

Jeff Garrett
Jeff Garrett

Reputation: 7528

Look at the types:

auto zip = ranges::views::zip(v1, v2);
// ranges::zip_view<
//   ranges::ref_view<std::vector<int>>
//   ranges::ref_view<std::vector<char>>
// >

auto begin = std::begin(zip);
// ranges::basic_iterator<
//   ranges::iter_zip_with_view<
//     ranges::detail::indirect_zip_fn_,
//     ranges::ref_view<std::vector<int>>,
//     ranges::ref_view<std::vector<char>>
//   >::cursor<false>
// >

using traits = std::iterator_traits<decltype(begin)>;
static_assert(std::is_same_v<traits::value_type, std::pair<int, char>>);
static_assert(std::is_same_v<traits::reference, ranges::common_pair<int&, char&>>);

The value_type type is a std::pair of values. The reference type is a ranges::common_pair of references.

std::sort uses std::iter_swap which is specified in terms of dereferencing the iterators and calling std::swap. So std::sort will try to swap two ranges::common_pair of references. On the other hand, ranges::actions::sort uses ranges::iter_swap which is customized to handle pairs and tuples of references.

Pairs and tuples of references are/were second class citizens in the standard library.

ranges::actions::unique requires an erasable range which evidently this does not satisfy.

Added

The documentation for range-v3 is sparse. To find information such as the above, there is of course looking at the source for range-v3, quick experimentation on godbolt.org (range-v3 is an available library), and "standard" C++ tricks to find the type of a variable (e.g., calling a function template which is declared but not defined, with the variable's type as the template argument, and seeing which instantiation is called).

To comment more on unique, ranges::action::unique does not return an iterator. It erases the non-unique elements and returns a range (see the source). In part of the compiler error which was omitted, the error references the fact that the range is not erasable (buried in a huge error).

ranges::unique returns an iterator and can be called without error. This is a basic_iterator<...>. One option is to use ranges::distance to find the distance from the begin zip iterator and use this to get the underlying iterator:

auto zip_uniq_iter = ranges::unique(zip);
auto first_uniq_iter = std::next(v1.begin(), ranges::distance(ranges::begin(zip), zip_uniq_iter));

Upvotes: 5

Related Questions