There're many great answers, but for some reason most of them don't consider returning the results of applying f to our tuple...
or did I overlook it? Anyway, here's yet another way you can do that:
Doing Foreach with style (debatable)
auto t = std::make_tuple(1, "two", 3.f);
t | foreach([](auto v){ std::cout << v << " "; });
And returning from that:
auto t = std::make_tuple(1, "two", 3.f);
auto sizes = t | foreach([](auto v) {
return sizeof(v);
});
sizes | foreach([](auto v) {
std::cout << v;
});
Implementation (pretty simple one)
Edit: it gets a little messier.
I won't include some metaprogramming boilerplate here, for it will definitely make things less readable and besides, I believe those have already been answered somewhere on stackoverflow.
In case you're feeling lazy, feel free to peek into my github repo for implementation of both
#include <utility>
// Optional includes, if you don't want to implement it by hand or google it
// you can find it in the repo (link below)
#include "typesystem/typelist.hpp"
// used to check if all return types are void,
// making it a special case
// (and, alas, not using constexpr-if
// for the sake of being compatible with C++14...)
template <bool Cond, typename T, typename F>
using select = typename std::conditional<Cond, T, F>::type;
template <typename F>
struct elementwise_apply {
F f;
};
template <typename F>
constexpr auto foreach(F && f) -> elementwise_apply<F> { return {std::forward<F>(f)}; }
template <typename R>
struct tuple_map {
template <typename F, typename T, size_t... Is>
static constexpr decltype(auto) impl(std::index_sequence<Is...>, F && f, T&& tuple) {
return R{ std::forward<F>(f)( std::get<Is>(tuple) )... };
}
};
template<>
struct tuple_map<void> {
template <typename F, typename T, size_t... Is>
static constexpr void impl(std::index_sequence<Is...>, F && f, T&& tuple) {
[[maybe_unused]] std::initializer_list<int> _ {((void)std::forward<F>(f)( std::get<Is>(tuple) ), 0)... };
}
};
template <typename F, typename... Ts>
constexpr decltype(auto) operator| (std::tuple<Ts...> & t, fmap<F> && op) {
constexpr bool all_void = core::Types<decltype( std::move(op).f(std::declval<Ts&>()) )...>.all( core::is_void );
using R = meta::select<all_void, void, std::tuple<decltype(std::move(op).f(std::declval<Ts&>()))...>>;
return tuple_map<R>::impl(std::make_index_sequence<sizeof...(Ts)>{}, std::move(op).f, t);
}
template <typename F, typename... Ts>
constexpr decltype(auto) operator| (std::tuple<Ts...> const& t, fmap<F> && op) {
constexpr bool all_void = check if all "decltype( std::move(op).f(std::declval<Ts>()) )..." types are void, since then it's a special case
// e.g. core::Types<decltype( std::move(op).f(std::declval<Ts>()) )...>.all( core::is_void );
using R = meta::select<all_void, void, std::tuple<decltype(std::move(op).f(std::declval<Ts const&>()))...>>;
return tuple_map<R>::impl(std::make_index_sequence<sizeof...(Ts)>{}, std::move(op).f, t);
}
template <typename F, typename... Ts>
constexpr decltype(auto) operator| (std::tuple<Ts...> && t, fmap<F> && op) {
constexpr bool all_void = core::Types<decltype( std::move(op).f(std::declval<Ts&&>()) )...>.all( core::is_void );
using R = meta::select<all_void, void, std::tuple<decltype(std::move(op).f(std::declval<Ts&&>()))...>>;
return tuple_map<R>::impl(std::make_index_sequence<sizeof...(Ts)>{}, std::move(op).f, std::move(t));
}
Yeah, that would be much nicer if we were to use C++17
This is also an example of std::moving object's members, for which I'll better refer to this nice brief article
P.S. If you're stuck checking if all "decltype( std::move(op).f(std::declval()) )..." types are void
you can find some metaprogramming library, or, if those libraries seem too hard to grasp (which some of them may be due to some crazy metaprogramming tricks), you know where to look