Es gibt viele tolle Antworten, aber aus irgendeinem Grund berücksichtigen die meisten von ihnen nicht die Rückgabe der Ergebnisse der Anwendung von f auf unser Tupel... oder habe ich das übersehen? Wie auch immer, hier ist eine weitere Möglichkeit, wie Sie das tun können:
Foreach mit Stil ausführen (fraglich)
auto t = std::make_tuple(1, "two", 3.f);
t | foreach([](auto v){ std::cout << v << " "; });
Und davon zurückkehren:
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;
});
Umsetzung (ziemlich einfach)
Edit: Es wird noch ein bisschen unübersichtlicher.
Ich werde hier keine Metaprogrammier-Kauderwelsch einfügen, denn das macht die Sache definitiv weniger lesbar und außerdem glaube ich, dass diese Fragen bereits irgendwo auf Stackoverflow beantwortet wurden. Falls Sie sich faul fühlen, können Sie gerne einen Blick in meine Github-Repository für die Umsetzung der beiden
#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));
}
Ja, das wäre viel schöner, wenn wir C++17 verwenden würden.
Dies ist auch ein Beispiel für die Mitglieder des std::moving-Objekts, für das ich mich besser auf diesen netten kurzen Artikel beziehe Artikel
P.S. Wenn Sie nicht mehr wissen, ob alle "decltype( std::move(op).f(std::declval()) )..."-Typen ungültig sind sind, können Sie eine Metaprogrammierbibliothek finden, oder, wenn diese Bibliotheken zu schwer zu verstehen sind (was einige von ihnen aufgrund einiger verrückter Metaprogrammiertricks sein können), wissen Sie, wo Sie siehe