Skip to content

CppNorth23 Example: Piping with variadic range adaptors #8

@lukasfro

Description

@lukasfro

Hi Conor,

I was watching your latest talk about the algorithms/range adaptors that C++23 introduces. I'm excited to see you advertising this "sequential programming" style! I'm already a big fan of ranges, but mostly use the range-v3 library at the moment.

In the talk, you mention at around 1:13h that you could not come up with a more elegant way of simplifying the Sushi-for-Two problem using range-v3. You mostly criticize the issue that one cannot pipe into variadiac templates (like zip_with and concat). I fully agree that the Circle compiler has a neat solution for the general pipeline operator. However, I still believe that you can make the example a bit more elegant with "plain" range-v3 (see a full example in compiler explorer). I'm eager to hear what you think about my approach 🙂

  1. Make a custom pairwise_transform adaptor

You have to introduce the temporary variables indices and deltas because you need to pipe them twice into the zip_with range adaptor. However, you could simply make a custom range adaptor, i.e., the pairwise_transform from C++23. Importantly, I make use of the make_pipeable helper that not too many people utilize. While this does not solve the underlying problem that one cannot pipe into variadic templates, in your example you could get rid of the temporary variables.

template <typename Rng, typename Fun>
auto pairwise_transform(Rng&& rng, Fun fun) {
    return rv::zip_with(fun, rng, rng | rv::drop(1));
}
template <typename Fun>
auto pairwise_transform(Fun fun) {
    return ranges::make_pipeable(
        [fun](auto&& rng) { return pairwise_transform(rng, fun); });
}
  1. Make custom prepend/append adaptors

These adaptors solve the "nonlinearity" issue with concat. I, personally, hated that concat could not be piped into and it really breaks the flow of reading the code. Hence, I made myself the following two neat helpers that solved most of my problems. Of course these can be extended to appending/prepending ranges instead of single elements.

// Adds an element to the beginning of a range.
template <typename Rng, typename T>
auto prepend(Rng&& rng, T t) {
    return rv::concat(rv::single(t), rng);
}
template <typename T>
auto prepend(T t) {
    return ranges::make_pipeable([t](auto&& rng) { return prepend(rng, t); });
}

// Adds an element to the end of a range.
template <typename Rng, typename T>
auto append(Rng&& rng, T t) {
    return rv::concat(rng, rv::single(t));
}
template <typename T>
auto append(T t) {
    return ranges::make_pipeable([t](auto&& rng) { return append(rng, t); });

Cheers and kind regards,
Lukas

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions