164 Stimmen

Wie kann man über die Elemente eines std::tuple iterieren?

Wie kann ich über ein Tupel iterieren (mit C++11)? Ich habe das Folgende versucht:

for(int i=0; i<std::tuple_size<T...>::value; ++i) 
  std::get<i>(my_tuple).do_sth();

aber das funktioniert nicht:

Fehler 1: sorry, unimplementiert: kann 'Listener ...' nicht in eine Argumentliste mit fester Länge erweitern.
Fehler 2: i kann nicht in einem konstanten Ausdruck vorkommen.

Wie kann ich also korrekt über die Elemente eines Tupels iterieren?

1voto

joki Punkte 6196

Von allen Antworten, die ich hier gesehen habe, ici y ici mochte ich @sigidagi Methode der Iteration am besten. Leider ist seine Antwort sehr langatmig, was meiner Meinung nach die inhärente Klarheit verdunkelt.

Dies ist meine Version seiner Lösung, die prägnanter ist und mit std::tuple , std::pair y std::array .

template<typename UnaryFunction>
void invoke_with_arg(UnaryFunction)
{}

/**
 * Invoke the unary function with each of the arguments in turn.
 */
template<typename UnaryFunction, typename Arg0, typename... Args>
void invoke_with_arg(UnaryFunction f, Arg0&& a0, Args&&... as)
{
    f(std::forward<Arg0>(a0));
    invoke_with_arg(std::move(f), std::forward<Args>(as)...);
}

template<typename Tuple, typename UnaryFunction, std::size_t... Indices>
void for_each_helper(Tuple&& t, UnaryFunction f, std::index_sequence<Indices...>)
{
    using std::get;
    invoke_with_arg(std::move(f), get<Indices>(std::forward<Tuple>(t))...);
}

/**
 * Invoke the unary function for each of the elements of the tuple.
 */
template<typename Tuple, typename UnaryFunction>
void for_each(Tuple&& t, UnaryFunction f)
{
    using size = std::tuple_size<typename std::remove_reference<Tuple>::type>;
    for_each_helper(
        std::forward<Tuple>(t),
        std::move(f),
        std::make_index_sequence<size::value>()
    );
}

Demo: coliru

C++14's std::make_index_sequence kann umgesetzt werden für C++11 .

1voto

bit2shift Punkte 586

Vielleicht habe ich diesen Zug verpasst, aber ich werde ihn für die Zukunft hier aufbewahren.
Hier ist mein Konstrukt auf dieser Grundlage 回答 und auf diese . :

#include <tuple>
#include <utility>

template<std::size_t N>
struct tuple_functor
{
    template<typename T, typename F>
    static void run(std::size_t i, T&& t, F&& f)
    {
        const std::size_t I = (N - 1);
        switch(i)
        {
        case I:
            std::forward<F>(f)(std::get<I>(std::forward<T>(t)));
            break;

        default:
            tuple_functor<I>::run(i, std::forward<T>(t), std::forward<F>(f));
        }
    }
};

template<>
struct tuple_functor<0>
{
    template<typename T, typename F>
    static void run(std::size_t, T, F){}
};

Sie verwenden es dann wie folgt:

template<typename... T>
void logger(std::string format, T... args) //behaves like C#'s String.Format()
{
    auto tp = std::forward_as_tuple(args...);
    auto fc = [](const auto& t){std::cout << t;};

    /* ... */

    std::size_t some_index = ...
    tuple_functor<sizeof...(T)>::run(some_index, tp, fc);

    /* ... */
}

Es könnte Raum für Verbesserungen geben.


Gemäß dem Code des Auftraggebers würde es so aussehen:

const std::size_t num = sizeof...(T);
auto my_tuple = std::forward_as_tuple(t...);
auto do_sth = [](const auto& elem){/* ... */};
for(int i = 0; i < num; ++i)
    tuple_functor<num>::run(i, my_tuple, do_sth);

1voto

Thomas Legris Punkte 124

In Erweiterung der Antwort von @Stypox können wir ihre Lösung allgemeiner gestalten (ab C++17). Durch Hinzufügen eines aufrufbaren Funktionsarguments:

template<size_t I = 0, typename... Tp, typename F>
void for_each_apply(std::tuple<Tp...>& t, F &&f) {
    f(std::get<I>(t));
    if constexpr(I+1 != sizeof...(Tp)) {
        for_each_apply<I+1>(t, std::forward<F>(f));
    }
}

Dann brauchen wir eine Strategie, um jeden Typ zu besuchen.

Beginnen wir mit einigen Hilfsmitteln (die ersten beiden stammen von cppreference):

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template<class ... Ts> struct variant_ref { using type = std::variant<std::reference_wrapper<Ts>...>; };

variant_ref wird verwendet, damit der Zustand der Tupel geändert werden kann.

Verwendung:

std::tuple<Foo, Bar, Foo> tuples;

for_each_apply(tuples,
               [](variant_ref<Foo, Bar>::type &&v) {
                   std::visit(overloaded {
                       [](Foo &arg) { arg.foo(); },
                       [](Bar const &arg) { arg.bar(); },
                   }, v);
               });

Ergebnis:

Foo0
Bar
Foo0
Foo1
Bar
Foo1

Der Vollständigkeit halber, hier sind meine Bar & Foo :

struct Foo {
    void foo() {std::cout << "Foo" << i++ << std::endl;}
    int i = 0;
};
struct Bar {
    void bar() const {std::cout << "Bar" << std::endl;}
};

0voto

tmaric Punkte 5025

Ich bin über dasselbe Problem bei der Iteration über ein Tupel von Funktionsobjekten gestolpert, daher hier eine weitere Lösung:

#include <tuple> 
#include <iostream>

// Function objects
class A 
{
    public: 
        inline void operator()() const { std::cout << "A\n"; };
};

class B 
{
    public: 
        inline void operator()() const { std::cout << "B\n"; };
};

class C 
{
    public:
        inline void operator()() const { std::cout << "C\n"; };
};

class D 
{
    public:
        inline void operator()() const { std::cout << "D\n"; };
};

// Call iterator using recursion.
template<typename Fobjects, int N = 0> 
struct call_functors 
{
    static void apply(Fobjects const& funcs)
    {
        std::get<N>(funcs)(); 

        // Choose either the stopper or descend further,  
        // depending if N + 1 < size of the tuple. 
        using caller = std::conditional_t
        <
            N + 1 < std::tuple_size_v<Fobjects>,
            call_functors<Fobjects, N + 1>, 
            call_functors<Fobjects, -1>
        >;

        caller::apply(funcs); 
    }
};

// Stopper.
template<typename Fobjects> 
struct call_functors<Fobjects, -1>
{
    static void apply(Fobjects const& funcs)
    {
    }
};

// Call dispatch function.
template<typename Fobjects>
void call(Fobjects const& funcs)
{
    call_functors<Fobjects>::apply(funcs);
};

using namespace std; 

int main()
{
    using Tuple = tuple<A,B,C,D>; 

    Tuple functors = {A{}, B{}, C{}, D{}}; 

    call(functors); 

    return 0; 
}

Sortie :

A 
B 
C 
D

0voto

Alex Vask Punkte 51

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X