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; });
1 Answer 1
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 decltype
s and std::forward
s 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++.
-
\$\begingroup\$ I wanted to separate the loop "body" from the arguments. We usually don't think of this sort of thing as "looping". \$\endgroup\$user1095108– user10951082021年08月06日 14:27:05 +00:00Commented Aug 6, 2021 at 14:27
-
\$\begingroup\$ i.e. I wanted to achieve something like
pipe(1, 2, 3) | f;
\$\endgroup\$user1095108– user10951082021年08月06日 14:40:45 +00:00Commented 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– G. Sliepen2021年08月06日 15:35:29 +00:00Commented 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\$Toby Speight– Toby Speight2021年08月06日 15:42:21 +00:00Commented Aug 6, 2021 at 15:42 -
1\$\begingroup\$ if
std::tuple
were more inspired, we could just dostd::forward_as_tuple(1, 2, 3) | f
. \$\endgroup\$user1095108– user10951082021年08月06日 15:54:14 +00:00Commented Aug 6, 2021 at 15:54