# looping over variables of heterogeneous type

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; });


# 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++.

• I wanted to separate the loop "body" from the arguments. We usually don't think of this sort of thing as "looping". Aug 6, 2021 at 14:27
• i.e. I wanted to achieve something like pipe(1, 2, 3) | f; Aug 6, 2021 at 14:40
• @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! Aug 6, 2021 at 15:35
• 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. Aug 6, 2021 at 15:42
• if std::tuple were more inspired, we could just do std::forward_as_tuple(1, 2, 3) | f. Aug 6, 2021 at 15:54