8

In C++23, the ranges (sub)library has gained std::ranges::zip, which zips multiple ranges into a single range of std::tuple's (or pairs). This is nice, and precludes requiring implementing this ourselves, using boost::zip_iterator or resorting to this kind of a hack*.

However, we also get std::ranges::zip_transform. Why do we need it? After all , we can apply a ranges::views::transform to a zipped range, can't we? So, isn't zip_transform redundant?


* - that hack works well in C++11, and doesn't require tens of thousands of lines of code with concepts...

einpoklum
  • 118,144
  • 57
  • 340
  • 684

2 Answers2

8

If, in C++, the concepts of "the ordered parameters of a function" and "a tuple" were identical or easily interchangeable, then you would be right.

... unfortunately, that is not the case. The difference is that std::ranges::zip_transform cuts out the middle-man: Instead of constructing an std::tuple with each iterator advance and passing it to the transform function, references to the range elements themselves are passed to the function!

So, instead of writing something like:

auto add = [](std::tuple t) { 
    return std::get<0>(t) + std::get<1>(t) + std::get<2>(t); 
};
auto elementwise_sum = 
    std::views::zip(v1, v2, v3) | std::views::transform(add);

we can write, instead:

auto add = [](auto a, auto b, auto c) { return a + b + c; };
auto elementwise_sum = std::views::zip_transform(add, v1, v2, v3);

which I'm sure you'll agree is nicer.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
1

In addition to what @einpoklum has explained about zip_transform, it is also worth noting that zip_transform will always forward each elements in a zipped result to the function. Which means it cannot be used as a drop-in replacement for zip | transform:

std::vector<int> v1, v2;
auto func = [](std::tuple<int, int> t){ return double{}; };

views::zip(v1, v2) | views::transform(func); // ok

// views::zip_transform(func, v1, v2); // this will not work

The second one will not work because it must receive a function that takes 2 parameters.


Sidenote, there are also views::adjacent/views::adjacent_transform, and their pairwise specializations that behaves similar to zip/zip_transform.

Ranoiaetep
  • 5,872
  • 1
  • 14
  • 39
  • Why should the zip_transform work in your code snippet? Tuples don't get magically constructed. If I just have `func`, I can't call `func(12, 34)` after all. – einpoklum Nov 24 '22 at 08:24
  • @einpoklum I'm unsure what you were asking. It clearly commented that it wouldn't work in my snippet – Ranoiaetep Nov 24 '22 at 12:36
  • I'm asking what the motivation is for adding the line which doesn't work; it hints that one might have expected it to work, but it doesn't even though it might have. – einpoklum Nov 24 '22 at 14:26
  • @einpoklum The motivation is that based on the title of the question, one might expect `zip_transform(func, v1, v2)` be the shorthand/drop-in replacement for `zip(v1, v2) | transform(func)`, which is not true, which is what I was trying to demonstrate with my code. And since you already gave the correct way of using `zip_transform` in your answer, I didn't add that information in mine. – Ranoiaetep Nov 25 '22 at 00:27