2
\$\begingroup\$

These snippets are nothing special, but they remind us to sometimes loop without a for or a while loop. It is even possible to iterate over struct members in a similar manner.

template <typename ...A>
constexpr auto all_loop(A&& ...a) noexcept
{
  return [&](auto const f) noexcept(noexcept(
    (f(std::forward<decltype(a)>(a)), ...)))
    {
      (f(std::forward<decltype(a)>(a)), ...);
    };
}

template <typename ...A>
constexpr auto cond_loop(A&& ...a) noexcept
{
  return [&](auto const f) noexcept(noexcept(
    (f(std::forward<decltype(a)>(a)), ...)))
    {
      return (f(std::forward<decltype(a)>(a)) || ...);
    };
}

Usage:

all_loop(1, 2, 3, false, true, nullptr)([](auto&& v) { std::cout << v << std::endl; });

https://wandbox.org/permlink/kFnaNn1vPoS0lF4F

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

No need to return a lambda

I don't see the need for returning a lambda which you then have to invoke. Why not pass the function as the first parameter, similar to std::apply and std::invoke? I would rewrite all_loop() like so:

template <typename F, typename ...A>
constexpr void invoke_all(F f, A&& ...a)
noexcept(noexcept((f(std::forward<decltype(a)>(a)), ...)))
{
     (f(std::forward<decltype(a)>(a)), ...);
}

And then you can use it like so:

invoke_all([](auto&& v){std::cout << v << '\n';}, 1, 2, 3, false, true, nullptr);

If you really need to have it as a lambda, the caller can still do that themselves:

auto curried = [](auto const f){invoke_all(f, 1, 2, 3, false, true, nullptr);};
curried([](auto&& v){std::cout << v << '\n';});

Prefer '\n' over std::endl

Use '\n' instead of std::endl; the latter is equivalent to the former, but also forces the output to be flushed, which is normally not necessary and might impact performance.

Making it "pipeable"

I wanted to achieve something like pipe(1, 2, 3) | f;

You can do that as well, by creating a type that stores the values and overloads operator| to take any function. For example:

template <typename... A>
class pipe
{
    std::tuple<A...> a;
public:
    pipe(A&&... a): a{a...} {}
    auto operator|(auto&& f) {
        std::apply([&](auto&&... a){(f(a), ...);}, a);
    }
};

(I left all the decltypes and std::forwards as an excercise to the reader.) Then you can indeed write:

pipe(1, 2, 3, false, true, nullptr) | [](auto&& v) { std::cout << v << '\n'; };

But I would not use this, and rather stick to the idiomatic way of calling things in C++.

\$\endgroup\$
9
  • \$\begingroup\$ I wanted to separate the loop "body" from the arguments. We usually don't think of this sort of thing as "looping". \$\endgroup\$ Commented Aug 6, 2021 at 14:27
  • \$\begingroup\$ i.e. I wanted to achieve something like pipe(1, 2, 3) | f; \$\endgroup\$ Commented Aug 6, 2021 at 14:40
  • 1
    \$\begingroup\$ @user1095108 Yes, and then you also need to add the perfect forwarding. That's why I said I left it as an excercise. I think that should not be a problem, you did this correctly in your code! \$\endgroup\$
    – G. Sliepen
    Commented Aug 6, 2021 at 15:35
  • 1
    \$\begingroup\$ Oh, I wasn't claiming that! Only saying that like we got used to << and >> not just being bit-shift but also streaming back in the 1980s, and that other libraries have followed that lead, we might start to see use of | as a composition operator more widely, following its use in Ranges. I'm not really advocating that in 2021, though - just musing on the way idioms can change. \$\endgroup\$ Commented Aug 6, 2021 at 15:42
  • 1
    \$\begingroup\$ if std::tuple were more inspired, we could just do std::forward_as_tuple(1, 2, 3) | f. \$\endgroup\$ Commented Aug 6, 2021 at 15:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.