3

I am trying to learn C++20/C++23 features and am stuck on the following problem. Suppose there are two vectors of arrays of the same size:

std::vector<std::array<int, 2>> v1 = { {1, 2}, {3, 4}, {5, 6} };
std::vector<std::array<int, 2>> v2 = { {7, 8}, {9, 10}, {11, 12} };

I would like to create a view that would be a double zip application to these vectors: first to the vectors themselves, and then to the elements of the resulting view.

For the two vectors above, the resulting view will look something like this:

[[(1, 7), (2, 8)], [(3, 9), (4, 10)], [(5, 11), (6, 12)]]

Also, I would like to be able to apply ranges algorithm (sort, unique, etc.) to it as well, i.e. the view elements have to be swappable.

I have tried the following:

auto result = std::ranges::views::zip(v1, v2)
            | std::ranges::views::transform([](const auto& arrays) {
                  return std::ranges::views::zip(std::get<0>(arrays), std::get<1>(arrays));
              });

This gives the correct result, but the resulting view can only be read from, but not processed by algorithms.

What is the most suitable way to solve this problem using the modern C++20 (23) ranges library?


An expanded example of what I'm trying to do, since there are questions in the comments.

Suppose there are two vectors:

std::vector<std::array<int, 3>> v1 = { {4, 5, 6}, {1, 2, 3}, {5, 6, 4} };
std::vector<std::array<int, 3>> v2 = { {15, 14, 13}, {12, 10, 11}, {12, 10, 11} };

I want to zip these two vectors to get a view that looks like this:

[[(4, 15), (5, 14), (6, 13)], [(1, 12), (2, 10), (3, 11)], [(5, 12), (6, 10), (4, 11)]]

With my approach above, I can get this and print it out. I can even swap the elements of the array, but I can't swap the arrays themselves. Here's what I mean.

First, I want to process this view with:

auto get_first = [](const auto& p)
{
   return std::get<0>(p);
};

std::ranges::for_each(view, [&get_first](auto a)
{
    std::ranges::rotate(a, std::ranges::max_element(a, std::ranges::less{}, get_first));
});

To get such view:

[[(6, 13), (4, 15), (5, 14)], [(3, 11), (1, 12), (2, 10)], [(6, 10), (4, 11), (5, 12)]]

And that will work.

Second, I want to sort this view to remove duplicates in the future:

using namespace std::placeholders;
auto lex_less = std::bind(std::ranges::lexicographical_compare, _1, _2, std::ranges::less{}, get_first, get_first);
std::ranges::sort(result, lex_less);

That should lead to this view:

[[(3, 11), (1, 12), (2, 10)], [(6, 13), (4, 15), (5, 14)], [(6, 10), (4, 11), (5, 12)]]

And, as a result, v1 and v2 would be:

v1: [[3, 1, 2], [6, 4, 5], [6, 4, 5]]
v2: [[11, 12, 10], [13, 15, 14], [10, 11, 12]]

But it won't work, because the iterators of the resulting transform view are not swappable (and it looks like they don't have to be). So I'm looking for a way to get something similar using other approaches. Since I am learning modern C++, I would like to know if it is possible to achieve the described behavior with it.

lc_vorenus
  • 31
  • 3
  • 1
    "apply ranges algorithm (sort, unique, etc.) to it" - what's "it"? what are you trying to sort? – T.C. May 18 '23 at 21:38
  • @T.C. Added clarification – lc_vorenus May 19 '23 at 08:35
  • For reasons I can't understand, `auto get_first = BOOST_HOF_LIFT(std::get<0>);` fixes everything, and everything I have tried to figure out what it is doing different to your lambda fails. https://godbolt.org/z/14P1asTjn – Caleth May 19 '23 at 10:15
  • @Caleth Everything is working because of this line: `std::ranges::sort(std::views::zip(v1, v2), {}, get_first);`, even with my lambda, but fails with `std::ranges::sort(result, {}, get_first);`, from my example. Someone can use your approach in practice (which is what I am doing now), but I want to know if it is possible to create such a "universal" view. – lc_vorenus May 19 '23 at 10:33

2 Answers2

2

The easiest way I think is:

views::zip(v1, v2)
| views::transform(hof::unpack(views::zip))

hof::unpack is a function adaptor from Boost.HOF such that unpack(f)(x) evaluates as std::apply(f, x). Which is what you want here - you have a range of tuples, and you want to unpack each tuple into zip.

That's what you're doing by hand here, since you know each tuple has size 2:

views::zip(v1, v2)
| views::transform([](const auto& arrays) {
    return views::zip(std::get<0>(arrays), std::get<1>(arrays));
});

But either way, you're get a range of prvalue tuples of mutable references - so they're still mutable.


Note that you don't have to write std::ranges::views::meow, you can just write std::views::meow.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks for reply, but seems like your solution produces a read-only view as well: https://godbolt.org/z/K7GE3fe9x. Regarding the suggestion to remove const: we cannot do this because the transforming function must satisfy the [regular_invocable](https://en.cppreference.com/w/cpp/concepts/invocable) concept. – lc_vorenus May 18 '23 at 21:40
  • 6
    Even terser: `std::views::zip_transform(std::views::zip, v1, v2)` – T.C. May 18 '23 at 21:43
  • `regular_invocable` certainly doesn't require taking by `const&` (not that it matters here anyway). – T.C. May 18 '23 at 21:44
  • @lc_vorenus `regular_invocable` doesn't require that all the parameters are `const`, it just requires that it gives you the same result every time. But also, what is that `iter_swap` even supposed to do? You have a range of prvalue `zip_view`s, did you mean to swap the underlying elements, [like this maybe](https://godbolt.org/z/cddYvh89z)? – Barry May 18 '23 at 21:44
  • @Barry You swap the elements of the arrays, but I want to swap the arrays themselves. My code has a rather complex algorithm that rotates the arrays in some way, and then sorts them in lexicographical order and removes duplicates of those arrays. Now I want to generalize my algorithm so that I can add additional attributes to the elements of these arrays without introducing new structures or copying data. – lc_vorenus May 18 '23 at 22:11
  • @T.C. But for some reason the code only compiles if I pass either by constant reference or as a copy. In both cases iterators of the result are not swappable. – lc_vorenus May 18 '23 at 22:14
  • @lc_vorenus I have no idea what you're trying to do. It would help if you added more detail to your question. – Barry May 18 '23 at 23:49
  • @Barry I updated my question with an example. – lc_vorenus May 19 '23 at 08:33
2

You don't want a universal view. You want a bunch of views specific to their use cases. If you want to sort the arrays, have a view of the arrays.

auto get_first = BOOST_HOF_LIFT(std::get<0>);
std::vector<std::array<int, 3>> v1 = {{4, 5, 6}, {1, 2, 3}, {5, 6, 4}};
std::vector<std::array<int, 3>> v2 = {{15, 14, 13}, {12, 10, 11}, {12, 10, 11}};

auto innermost_view = std::views::zip_transform(std::views::zip, v1, v2);
std::ranges::for_each(innermost_view, [](auto a) {
    std::ranges::rotate(a, std::ranges::max_element(a, {}, get_first));
});

auto middle_view = std::views::zip(v1, v2);
std::ranges::sort(middle_view, {}, get_first);

See it on godbolt

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • You are right, good point! However, my question is more theoretical than practical. It is possible to learn a lot while solving such problems. In practice I do things more simply. – lc_vorenus May 19 '23 at 10:52