Wie kann man generische Berechnungen über heterogene Argumentpakete einer variablen Vorlagenfunktion durchführen?

77

PRÄMISSE:

Nachdem ich ein wenig mit verschiedenen Vorlagen herumgespielt hatte, wurde mir klar, dass es bald ziemlich umständlich wird, etwas zu erreichen, das etwas über die trivialen Metaprogrammieraufgaben hinausgeht. Insbesondere fand ich mich für eine Art und Weise will auszuführen generische Operationen über ein Argument Pack wie Iterierte , Split , Schleife in einer std::for_each-ähnlichen Art und Weise, und so weiter.

Nachdem ich diesen Vortrag von Andrei Alexandrescu aus C ++ und Beyond 2012 über die Wünschbarkeit von static ifC ++ (ein Konstrukt aus der Programmiersprache D ) gesehen hatte, hatte ich das Gefühl, dass auch eine Art von Konstrukt static fornützlich sein würde - und ich denke, dass mehr dieser staticKonstrukte dies könnten Nutzen bringen.

Also begann ich mich zu fragen, ob es eine Möglichkeit gibt, so etwas für Argumentpakete einer variadischen Vorlagenfunktion ( Pseudocode ) zu erreichen:

template<typename... Ts>
void my_function(Ts&&... args)
{
    static for (int i = 0; i < sizeof...(args); i++) // PSEUDO-CODE!
    {
        foo(nth_value_of<i>(args));
    }
}

Was zur Kompilierungszeit in so etwas übersetzt werden würde :

template<typename... Ts>
void my_function(Ts&&... args)
{
    foo(nth_value_of<0>(args));
    foo(nth_value_of<1>(args));
    // ...
    foo(nth_value_of<sizeof...(args) - 1>(args));
}

Im Prinzip static_forwürde eine noch aufwendigere Verarbeitung ermöglichen:

template<typename... Ts>
void foo(Ts&&... args)
{
    constexpr s = sizeof...(args);

    static for (int i = 0; i < s / 2; i++)
    {
        // Do something
        foo(nth_value_of<i>(args));
    }

    static for (int i = s / 2; i < s; i++)
    {
        // Do something different
        bar(nth_value_of<i>(args));
    }
}

Oder für eine ausdrucksstärkere Sprache wie diese:

template<typename... Ts>
void foo(Ts&&... args)
{
    static for_each (auto&& x : args)
    {
        foo(x);
    }
}

VERWANDTE ARBEITEN:

Ich habe im Web gesucht und festgestellt, dass tatsächlich etwas existiert:

  • Dieser Link beschreibt, wie ein Parameterpaket in einen Boost.MPL-Vektor konvertiert wird. Dies führt jedoch nur zur Hälfte (wenn nicht weniger) zum Ziel.
  • Diese Frage zu SO scheint eine ähnliche und leicht verwandte Metaprogrammierungsfunktion zu erfordern (Aufteilung eines Argumentationspakets in zwei Hälften) - tatsächlich gibt es mehrere Fragen zu SO, die mit diesem Problem in Zusammenhang zu stehen scheinen, aber keine der Antworten I. habe gelesen, löst es meiner Meinung nach zufriedenstellend;
  • Boost.Fusion definiert Algorithmen zum Konvertieren eines Argumentpakets in ein Tupel , aber ich würde es vorziehen:
    1. keine unnötigen Provisorien zu erstellen , um Argumente zu enthalten, die perfekt an einige generische Algorithmen weitergeleitet werden können (und sollten);
    2. haben eine kleine, in sich geschlossene Bibliothek, um dies zu tun, während Boost.Fusion wahrscheinlich viel mehr Material enthält, als zur Behebung dieses Problems benötigt wird.

FRAGE:

Gibt es eine relativ einfache Möglichkeit, möglicherweise durch eine Vorlagen-Metaprogrammierung, das zu erreichen, wonach ich suche, ohne die Einschränkungen der bestehenden Ansätze zu beeinträchtigen?

Andy Prowl
quelle
Wenn foo etwas zurückgibt, können Sie einfach eat (foo (args) ...) schreiben, wobei eat eine Funktion ist, die nichts mit ihren Argumenten macht. Benötigt einige Anpassungen für Funktionen, die void zurückgeben, oder wenn Sie die Ausführungsreihenfolge angeben möchten (dies wurde im Usenet diskutiert, wahrscheinlich comp.lang.c ++. Moderiert, obwohl ich es im Moment nicht finden kann). Es wurde besprochen, um zu erlaubenfoo(args);...
Marc Glisse
@MarcGlisse: Du hast einen Punkt, und das habe ich tatsächlich versucht. Das Problem bei dieser Lösung ist, dass C ++ keine Reihenfolge für die Auswertung von Funktionsargumenten garantiert, was beim Iterieren häufig erwünscht ist (abgesehen von der Tatsache, dass Funktionen einen Wert zurückgeben müssten, selbst wenn dies nicht erforderlich ist). aber das ist gering).
Andy Prowl
Ich denke, es gibt eine Variante, in der die Reihenfolge der Auswertung der Argumente angegeben ist, möglicherweise innerhalb von {} (Initialisierungsliste). Was den Rückgabetyp betrifft, können Sie wahrscheinlich (foo (args), 0) ... oder einen anderen Trick ausführen.
Marc Glisse
@MarcGlisse: Es könnte der Fall sein, bitte probieren Sie es aus und verbessern Sie meine Bibliothek, wenn Sie möchten. Ich wäre froh, wenn es besser gemacht werden könnte. Ehrlich gesagt denke ich, dass meine Lösung weder Overhead verursacht noch die Client-Seite einschränkt, was für meine Zwecke in Ordnung ist. aber das heißt natürlich nicht, dass es perfekt ist
Andy Prowl
1
FWIW, in der Vergangenheit hatte ich bereits überprüft, dass sowohl GCC als auch VC ++ Tupel von Referenzen in Release-Builds vollständig optimieren - identisch mit der Nichtverwendung von Tupeln (Testen mit Boost.Fusion).
ildjarn

Antworten:

65

Da ich mit dem, was ich gefunden habe, nicht zufrieden war, habe ich versucht, selbst eine Lösung zu finden, und schließlich eine kleine Bibliothek geschrieben , mit der generische Operationen für Argumentpakete formuliert werden können. Meine Lösung bietet folgende Funktionen:

  • Ermöglicht das Durchlaufen aller oder einiger Elemente eines Argumentpakets, möglicherweise angegeben durch Berechnen ihrer Indizes im Paket.
  • Ermöglicht die Weiterleitung berechneter Teile eines Argumentpakets an verschiedene Funktoren.
  • Erfordert nur das Einfügen einer relativ kurzen Header-Datei.
  • Verwendet die perfekte Weiterleitung in großem Umfang, um starkes Inlining zu ermöglichen, und vermeidet unnötige Kopien / Verschiebungen, um einen minimalen Leistungsverlust zu ermöglichen.
  • Die interne Implementierung der iterierenden Algorithmen basiert auf der Optimierung der leeren Basisklasse, um den Speicherverbrauch zu minimieren.
  • Es ist einfach (relativ, wenn man bedenkt, dass es sich um eine Meta-Programmierung für Vorlagen handelt), zu erweitern und anzupassen.

Ich werde zuerst zeigen, was getan werden kann mit der Bibliothek gemacht werden kann, und dann ihre Implementierung veröffentlichen .

ANWENDUNGSFÄLLE

Hier ist ein Beispiel, wie die for_each_in_arg_pack() Funktion verwendet werden kann, um alle Argumente eines Pakets zu durchlaufen und jedes Argument in der Eingabe an einen vom Client bereitgestellten Funktor zu übergeben (natürlich muss der Funktor einen generischen Aufrufoperator haben, wenn das Argumentpaket Werte enthält heterogener Typen):

// Simple functor with a generic call operator that prints its input. This is used by the
// following functors and by some demonstrative test cases in the main() routine.
struct print
{
    template<typename T>
    void operator () (T&& t)
    {
        cout << t << endl;
    }
};

// This shows how a for_each_*** helper can be used inside a variadic template function
template<typename... Ts>
void print_all(Ts&&... args)
{
    for_each_in_arg_pack(print(), forward<Ts>(args)...);
}

Der printobige Funktor kann auch für komplexere Berechnungen verwendet werden. Hier ist insbesondere, wie man eine Teilmenge iterieren würde (in diesem Fall a Teilbereich ) der Argumente in einem Paket durchlaufen:

// Shows how to select portions of an argument pack and 
// invoke a functor for each of the selected elements
template<typename... Ts>
void split_and_print(Ts&&... args)
{
    constexpr size_t packSize = sizeof...(args);
    constexpr size_t halfSize = packSize / 2;

    cout << "Printing first half:" << endl;
    for_each_in_arg_pack_subset(
        print(), // The functor to invoke for each element
        index_range<0, halfSize>(), // The indices to select
        forward<Ts>(args)... // The argument pack
        );

    cout << "Printing second half:" << endl;
    for_each_in_arg_pack_subset(
        print(), // The functor to invoke for each element
        index_range<halfSize, packSize>(), // The indices to select
        forward<Ts>(args)... // The argument pack
        );
}

Manchmal möchte man vielleicht nur einen Teil eines Argumentationspakets an einen anderen variadischen Funktor weiterleiten, anstatt seine Elemente zu durchlaufen und jedes einzeln an einen nicht variadischen Funktor zu übergeben. Das ist was dieforward_subpack() ermöglicht Algorithmus:

// Functor with variadic call operator that shows the usage of for_each_*** 
// to print all the arguments of a heterogeneous pack
struct my_func
{
    template<typename... Ts>
    void operator ()(Ts&&... args)
    {
        print_all(forward<Ts>(args)...);
    }
};

// Shows how to forward only a portion of an argument pack 
// to another variadic functor
template<typename... Ts>
void split_and_print(Ts&&... args)
{
    constexpr size_t packSize = sizeof...(args);
    constexpr size_t halfSize = packSize / 2;

    cout << "Printing first half:" << endl;
    forward_subpack(my_func(), index_range<0, halfSize>(), forward<Ts>(args)...);

    cout << "Printing second half:" << endl;
    forward_subpack(my_func(), index_range<halfSize, packSize>(), forward<Ts>(args)...);
}

Für spezifischere Aufgaben ist es natürlich möglich, bestimmte Argumente in einem Paket durch Indizieren abzurufen . Dies nth_value_of()ermöglicht die Funktion zusammen mit ihren Helfern first_value_of()und last_value_of():

// Shows that arguments in a pack can be indexed
template<unsigned I, typename... Ts>
void print_first_last_and_indexed(Ts&&... args)
{
    cout << "First argument: " << first_value_of(forward<Ts>(args)...) << endl;
    cout << "Last argument: " << last_value_of(forward<Ts>(args)...) << endl;
    cout << "Argument #" << I << ": " << nth_value_of<I>(forward<Ts>(args)...) << endl;
}

Wenn das Argumentpaket andererseits homogen ist (dh alle Argumente haben den gleichen Typ), ist möglicherweise eine Formulierung wie die folgende vorzuziehen. Dasis_homogeneous_pack<> Metafunktion ermöglicht die Bestimmung, ob alle Typen in einem Parameterpaket homogen sind, und soll hauptsächlich in static_assert()Anweisungen verwendet werden:

// Shows the use of range-based for loops to iterate over a
// homogeneous argument pack
template<typename... Ts>
void print_all(Ts&&... args)
{
    static_assert(
        is_homogeneous_pack<Ts...>::value, 
        "Template parameter pack not homogeneous!"
        );

    for (auto&& x : { args... })
    {
        // Do something with x...
    }

    cout << endl;
}

Da Lambdas nur syntaktischer Zucker für Funktoren sind, können sie auch in Kombination mit den oben genannten Algorithmen verwendet werden. Bis generische Lambdas von C ++ unterstützt werden, ist dies jedoch nur für homogene Argumentpakete möglich . Das folgende Beispiel zeigt auch die Verwendung vonhomogeneous-type<> Metafunktion, die den Typ aller Argumente in einem homogenen Paket zurückgibt:

 // ...
 static_assert(
     is_homogeneous_pack<Ts...>::value, 
     "Template parameter pack not homogeneous!"
     );
 using type = homogeneous_type<Ts...>::type;
 for_each_in_arg_pack([] (type const& x) { cout << x << endl; }, forward<Ts>(args)...);

Dies ist im Grunde das, was die Bibliothek erlaubt, aber ich glaube es könnte sogar erweitert werden , um komplexere Aufgaben auszuführen.

IMPLEMENTIERUNG

Jetzt kommt die Implementierung, die an sich etwas schwierig ist, daher werde ich mich auf Kommentare verlassen, um den Code zu erklären und zu vermeiden, dass dieser Beitrag zu lang wird (vielleicht ist er es bereits):

#include <type_traits>
#include <utility>

//===============================================================================
// META-FUNCTIONS FOR EXTRACTING THE n-th TYPE OF A PARAMETER PACK

// Declare primary template
template<int I, typename... Ts>
struct nth_type_of
{
};

// Base step
template<typename T, typename... Ts>
struct nth_type_of<0, T, Ts...>
{
    using type = T;
};

// Induction step
template<int I, typename T, typename... Ts>
struct nth_type_of<I, T, Ts...>
{
    using type = typename nth_type_of<I - 1, Ts...>::type;
};

// Helper meta-function for retrieving the first type in a parameter pack
template<typename... Ts>
struct first_type_of
{
    using type = typename nth_type_of<0, Ts...>::type;
};

// Helper meta-function for retrieving the last type in a parameter pack
template<typename... Ts>
struct last_type_of
{
    using type = typename nth_type_of<sizeof...(Ts) - 1, Ts...>::type;
};

//===============================================================================
// FUNCTIONS FOR EXTRACTING THE n-th VALUE OF AN ARGUMENT PACK

// Base step
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
    typename std::enable_if<(I == 0), decltype(std::forward<T>(t))>::type
{
    return std::forward<T>(t);
}

// Induction step
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
    typename std::enable_if<(I > 0), decltype(
        std::forward<typename nth_type_of<I, T, Ts...>::type>(
            std::declval<typename nth_type_of<I, T, Ts...>::type>()
            )
        )>::type
{
    using return_type = typename nth_type_of<I, T, Ts...>::type;
    return std::forward<return_type>(nth_value_of<I - 1>((std::forward<Ts>(args))...));
}

// Helper function for retrieving the first value of an argument pack
template<typename... Ts>
auto first_value_of(Ts&&... args) ->
    decltype(
        std::forward<typename first_type_of<Ts...>::type>(
            std::declval<typename first_type_of<Ts...>::type>()
            )
        )
{
    using return_type = typename first_type_of<Ts...>::type;
    return std::forward<return_type>(nth_value_of<0>((std::forward<Ts>(args))...));
}

// Helper function for retrieving the last value of an argument pack
template<typename... Ts>
auto last_value_of(Ts&&... args) ->
    decltype(
        std::forward<typename last_type_of<Ts...>::type>(
            std::declval<typename last_type_of<Ts...>::type>()
            )
        )
{
    using return_type = typename last_type_of<Ts...>::type;
    return std::forward<return_type>(nth_value_of<sizeof...(Ts) - 1>((std::forward<Ts>(args))...));
}

//===============================================================================
// METAFUNCTION FOR COMPUTING THE UNDERLYING TYPE OF HOMOGENEOUS PARAMETER PACKS

// Used as the underlying type of non-homogeneous parameter packs
struct null_type
{
};

// Declare primary template
template<typename... Ts>
struct homogeneous_type;

// Base step
template<typename T>
struct homogeneous_type<T>
{
    using type = T;
    static const bool isHomogeneous = true;
};

// Induction step
template<typename T, typename... Ts>
struct homogeneous_type<T, Ts...>
{
    // The underlying type of the tail of the parameter pack
    using type_of_remaining_parameters = typename homogeneous_type<Ts...>::type;

    // True if each parameter in the pack has the same type
    static const bool isHomogeneous = std::is_same<T, type_of_remaining_parameters>::value;

    // If isHomogeneous is "false", the underlying type is the fictitious null_type
    using type = typename std::conditional<isHomogeneous, T, null_type>::type;
};

// Meta-function to determine if a parameter pack is homogeneous
template<typename... Ts>
struct is_homogeneous_pack
{
    static const bool value = homogeneous_type<Ts...>::isHomogeneous;
};

//===============================================================================
// META-FUNCTIONS FOR CREATING INDEX LISTS

// The structure that encapsulates index lists
template <unsigned... Is>
struct index_list
{
};

// Collects internal details for generating index ranges [MIN, MAX)
namespace detail
{
    // Declare primary template for index range builder
    template <unsigned MIN, unsigned N, unsigned... Is>
    struct range_builder;

    // Base step
    template <unsigned MIN, unsigned... Is>
    struct range_builder<MIN, MIN, Is...>
    {
        typedef index_list<Is...> type;
    };

    // Induction step
    template <unsigned MIN, unsigned N, unsigned... Is>
    struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
    {
    };
}

// Meta-function that returns a [MIN, MAX) index range
template<unsigned MIN, unsigned MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;

//===============================================================================
// CLASSES AND FUNCTIONS FOR REALIZING LOOPS ON ARGUMENT PACKS

// Implementation inspired by @jogojapan's answer to this question:
// http://stackoverflow.com/questions/14089637/return-several-arguments-for-another-function-by-a-single-function

// Collects internal details for implementing functor invocation
namespace detail
{
    // Functor invocation is realized through variadic inheritance.
    // The constructor of each base class invokes an input functor.
    // An functor invoker for an argument pack has one base class
    // for each argument in the pack

    // Realizes the invocation of the functor for one parameter
    template<unsigned I, typename T>
    struct invoker_base
    {
        template<typename F, typename U>
        invoker_base(F&& f, U&& u) { f(u); }
    };

    // Necessary because a class cannot inherit the same class twice
    template<unsigned I, typename T>
    struct indexed_type
    {
        static const unsigned int index = I;
        using type = T;
    };

    // The functor invoker: inherits from a list of base classes.
    // The constructor of each of these classes invokes the input
    // functor with one of the arguments in the pack.
    template<typename... Ts>
    struct invoker : public invoker_base<Ts::index, typename Ts::type>...
    {
        template<typename F, typename... Us>
        invoker(F&& f, Us&&... args)
            :
            invoker_base<Ts::index, typename Ts::type>(std::forward<F>(f), std::forward<Us>(args))...
        {
        }
    };
}

// The functor provided in the first argument is invoked for each
// argument in the pack whose index is contained in the index list
// specified in the second argument
template<typename F, unsigned... Is, typename... Ts>
void for_each_in_arg_pack_subset(F&& f, index_list<Is...> const& i, Ts&&... args)
{
    // Constructors of invoker's sub-objects will invoke the functor.
    // Note that argument types must be paired with numbers because the
    // implementation is based on inheritance, and one class cannot
    // inherit the same base class twice.
    detail::invoker<detail::indexed_type<Is, typename nth_type_of<Is, Ts...>::type>...> invoker(
        f,
        (nth_value_of<Is>(std::forward<Ts>(args)...))...
        );
}

// The functor provided in the first argument is invoked for each
// argument in the pack
template<typename F, typename... Ts>
void for_each_in_arg_pack(F&& f, Ts&&... args)
{
    for_each_in_arg_pack_subset(f, index_range<0, sizeof...(Ts)>(), std::forward<Ts>(args)...);
}

// The functor provided in the first argument is given in input the
// arguments in whose index is contained in the index list specified
// as the second argument.
template<typename F, unsigned... Is, typename... Ts>
void forward_subpack(F&& f, index_list<Is...> const& i, Ts&&... args)
{
    f((nth_value_of<Is>(std::forward<Ts>(args)...))...);
}

// The functor provided in the first argument is given in input all the
// arguments in the pack.
template<typename F, typename... Ts>
void forward_pack(F&& f, Ts&&... args)
{
    f(std::forward<Ts>(args)...);
}

FAZIT

Obwohl ich meine eigene Antwort auf diese Frage gegeben habe (und tatsächlich aufgrund dieser Tatsache), bin ich natürlich gespannt, ob es alternative oder bessere Lösungen gibt, die ich verpasst habe - abgesehen von den im Abschnitt "Verwandte Werke" genannten der Frage.

Andy Prowl
quelle
4
Dies verdient ehrlich gesagt mehr Gegenstimmen als es hat. Ich mochte die Idee von static ifund static forfür mich würde sie das Lesen, Schreiben und Verstehen von Metaprogrammierungen viel einfacher machen, aber es würde mir überhaupt nichts ausmachen, wenn diese in der Standardbibliothek implementiert wären.
Chris
2
@chris: Tatsächlich kann der größte Teil dieser Funktionalität mit Boost.Fusion implementiert werden, indem zuerst das Argumentpaket in ein Tupel konvertiert wird (als ich dies schrieb, wusste ich nicht, dass der Compiler das Tupel weg optimieren würde). Aber danke für Ihre Anerkennung :)
Andy Prowl
Hmm, ich wusste nicht, dass Boost Fusion das kann. Ich habe ehrlich gesagt sehr wenig Erfahrung mit Boost (ich habe es erst erfahren, nachdem ich mit C ++ 11 ausgestattet war), aber die Fusion-Bibliothek und einige andere haben mein Interesse geweckt.
Chris
Ich weiß, dass diese Antwort vor drei Jahren geschrieben wurde, aber ich hoffe, Sie konnten mir antworten: Warum haben Sie nicht last_type_ofmit einer O (1) -Implementierung geschrieben? Jeder schreibt eine 1024-Parameterfunktion (dh ich spreche nicht über Template-Deph-Probleme), aber Sie könnten die Kompilierungszeit verkürzen. Vielen Dank.
Manu343726
@ Manu343726: Das war erst vor 6 Monaten, nicht vor 3 Jahren: DI kümmerte sich nicht um Komplexität, da alles zur Kompilierungszeit erledigt wird, so dass die Komplexität der Berechnungen normalerweise kein Problem darstellt. Ich habe gerade die einfachste Implementierung geschrieben, die mir in den Sinn kam :)
Andy Prowl
10

Lassen Sie mich diesen Code basierend auf der Diskussion posten:

#include <initializer_list>
#define EXPAND(EXPR) std::initializer_list<int>{((EXPR),0)...}

// Example of use:
#include <iostream>
#include <utility>

void print(int i){std::cout << "int: " << i << '\n';}
int print(double d){std::cout << "double: " << d << '\n';return 2;}

template<class...T> void f(T&&...args){
  EXPAND(print(std::forward<T>(args)));
}

int main(){
  f();
  f(1,2.,3);
}

Ich habe den generierten Code mit überprüft g++ -std=c++11 -O1und mainenthält nur 3 Aufrufe an print, es gibt keine Spur der Erweiterungshelfer.

Marc Glisse
quelle
Ich schätze, Sie haben sich Zeit genommen, um meine Frage zu beantworten. Im Moment habe ich einen Kommentar zu Ihrer Lösung (bedeutet nicht, dass sie nicht behoben werden kann): Sie verwendet keine perfekte Weiterleitung. Mit intund doubles spielt das keine große Rolle, aber mit UDTs bedeutet dies, dass Kopien und Verschiebungen generiert werden. Und Sie können nicht EXPAND(print(args))in EXPAND (print (forward <T> (args))) wechseln , da der Makro-Präprozessor etwas Unhöfliches ruft.
Andy Prowl
Ich habe gerade vorwärts <T> hinzugefügt (danke, ich hatte vergessen, es hinzuzufügen) und der Makro-Präprozessor hat sich überhaupt nicht beschwert ...
Marc Glisse
Sie haben Recht, aus irgendeinem Grund nahm ich an, dass der Präprozessor keine eckigen Klammern mochte. nicht sicher warum. Ich denke, dies ist eine gültige Alternative zu meinem Ansatz für die einfacheren Fälle, in denen Sie alle Elemente eines Bereichs durchlaufen möchten. Es hat auch den Vorteil, dass die aufgerufene Funktion eine Funktionsvorlage und nicht nur ein Funktor sein kann, wie es meine Lösung erfordert.
Andy Prowl
Ich werde meine eigene Antwort akzeptieren, weil ich glaube, dass sie den allgemeineren Geist der Frage abdeckt (wie man generische Operationen an Argumentpaketen durchführt). Ihre Technik ist sehr interessant, da sie kompakt ist und keine Funktoren erfordert, aber für die gesamte Parameterliste gilt und es nicht ermöglicht, ein Paket einfach zu teilen oder ein Unterpaket auszuwählen, durch das die Iteration durchgeführt werden soll. Auf jeden Fall eine +1
Andy Prowl
In Ordnung. Beachten Sie, dass die beiden Ansätze leicht kombiniert werden können. Leiten Sie einmal weiter, um einen Indexbereich verfügbar zu machen, und verwenden Sie dann EXPAND, um den Code auszuführen, der sowohl das Objekt als auch seinen Index betrifft (es macht nichts aus, zwei Pakete gleichzeitig zu erweitern).
Marc Glisse
5

Verwenden einer Aufzählungslösung (ala Python).

Verwendung:

void fun(int i, size_t index, size_t size) {
    if (index != 0) {
        std::cout << ", ";
    }

    std::cout << i;

    if (index == size - 1) {
        std::cout << "\n";
    }
} // fun

enumerate(fun, 2, 3, 4);

// Expected output: "2, 3, 4\n"
// check it at: http://liveworkspace.org/code/1cydbw$4

Code:

// Fun: expects a callable of 3 parameters: Arg, size_t, size_t
// Arg: forwarded argument
// size_t: index of current argument
// size_t: number of arguments
template <typename Fun, typename... Args, size_t... Is>
void enumerate_impl(Fun&& fun, index_list<Is...>, Args&&... args) {
    std::initializer_list<int> _{
        (fun(std::forward<Args>(args), Is, sizeof...(Is)), 0)...
    };
    (void)_; // placate compiler, only the side-effects interest us
}

template <typename Fun, typename... Args>
void enumerate(Fun&& fun, Args&&... args) {
    enumerate_impl(fun,
                   index_range<0, sizeof...(args)>(),
                   std::forward<Args>(args)...);
}

Der Range Builder (aus Ihrer Lösung gestohlen):

// The structure that encapsulates index lists
template <size_t... Is>
struct index_list
{
};

// Collects internal details for generating index ranges [MIN, MAX)
namespace detail
{
    // Declare primary template for index range builder
    template <size_t MIN, size_t N, size_t... Is>
    struct range_builder;

    // Base step
    template <size_t MIN, size_t... Is>
    struct range_builder<MIN, MIN, Is...>
    {
        typedef index_list<Is...> type;
    };

    // Induction step
    template <size_t MIN, size_t N, size_t... Is>
    struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
    {
    };
}

// Meta-function that returns a [MIN, MAX) index range
template<size_t MIN, size_t MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;
Matthieu M.
quelle
@AndyProwl: Ich muss zugeben, dass ich in den letzten Jahren mehr als einmal von der Eleganz einer Reihe von Python- "Standard" -Funktionen überrascht war. Das enumerateBeispiel ist etwas, das ich wirklich vermisse, obwohl ich denke, dass es normalerweise mehr wäre for (auto p: enumerate(container))und ich nicht sicher bin, ob die beiden Versionen (Container-Iteration und Tupel-Iteration) gut zusammenleben würden :)
Matthieu M.
Ich muss hier beschämend meine vaste Unkenntnis von Python gestehen :-) Nun, es scheint, ich sollte anfangen, es zu studieren
Andy Prowl
@ AndyProwl: Die Funktion Aufzählung und das Modul itertools sind gute Ausgangspunkte :)
Matthieu M.
0

Die ... Notation hat einige interessante Optionen, wie:

template<typename T>
int print(const T& x) {
  std::cout << "<" << x << ">";
  return 0;
}

void pass(...) {}

template<typename... TS>
void printall(TS... ts){
  pass(print(ts)...);
}

Leider kenne ich keine Möglichkeit, die Reihenfolge zu erzwingen, in der die Druckfunktionen aufgerufen werden (umgekehrt, auf meinem Compiler). Beachten Sie, dass print etwas zurückgeben muss.

Dieser Trick kann nützlich sein, wenn Sie sich nicht für die Bestellung interessieren.

dspeyer
quelle
Unter gitorious.org/redistd/redistd/blobs/master/include/redi/… finden Sie eine Möglichkeit, die Bestellung zu erzwingen, indem Sie den Standardtrick der funktionalen Programmierung verwenden, indem Sie den Kopf des Parameterpakets drucken und dann den Schwanz rekursiv verarbeiten
Jonathan Wakely
0

Nachdem ich ein paar andere Beiträge gelesen und eine Weile gebastelt hatte, kam ich auf Folgendes (etwas ähnlich wie oben, aber die Implementierung ist etwas anders). Ich habe dies mit dem Visual Studio 2013-Compiler geschrieben.

Verwendung mit einem Lambda-Ausdruck -

static_for_each()(
    [](std::string const& str)
    {
        std::cout << str << std::endl;
    }, "Hello, ", "Lambda!");

Der Nachteil bei der Verwendung eines Lambdas ist, dass die Parameter vom gleichen Typ sein müssen, der in der Parameterliste des Lambdas angegeben ist. Dies bedeutet, dass es nur mit einem Typ funktioniert. Wenn Sie eine Vorlagenfunktion verwenden möchten, können Sie das nächste Beispiel verwenden.

Verwendung mit struct wrapper functor -

struct print_wrapper
{
    template <typename T>
    void operator()(T&& str)
    {
        std::cout << str << " ";
    }
};

// 
// A little test object we can use.
struct test_object
{
    test_object() : str("I'm a test object!") {}
    std::string str;
};

std::ostream& operator<<(std::ostream& os, test_object t)
{
    os << t.str;
    return os;
}

//
// prints: "Hello, Functor! 1 2 I'm a test object!"
static_for_each()(print_wrapper(), "Hello,", "Functor!", 1, 2.0f, test_object());

Auf diese Weise können Sie alle gewünschten Typen übergeben und mit dem Funktor bearbeiten. Ich fand das ziemlich sauber und funktioniert gut für das, was ich wollte. Sie können es auch mit einem Funktionsparameterpaket wie diesem verwenden -

template <typename T, typename... Args>
void call(T f, Args... args)
{
    static_for_each()(f, args...);
}

call(print_wrapper(), "Hello", "Call", "Wrapper!");

Hier ist die Implementierung -

// 
// Statically iterate over a parameter pack 
// and call a functor passing each argument.
struct static_for_each
{
private:
    // 
    // Get the parameter pack argument at index i.
    template <size_t i, typename... Args>
    static auto get_arg(Args&&... as) 
    -> decltype(std::get<i>(std::forward_as_tuple(std::forward<Args>(as)...)))
    {
        return std::get<i>(std::forward_as_tuple(std::forward<Args>(as)...));
    }

    //
    // Recursive template for iterating over 
    // parameter pack and calling the functor.
    template <size_t Start, size_t End>
    struct internal_static_for
    {
        template <typename Functor, typename... Ts>
        void operator()(Functor f, Ts&&... args)
        {
            f(get_arg<Start>(args...));
            internal_static_for<Start + 1, End>()(f, args...);
        }
    };

    //
    // Specialize the template to end the recursion.
    template <size_t End>
    struct internal_static_for<End, End>
    {
        template <typename Functor, typename... Ts>
        void operator()(Functor f, Ts&&... args){}
    };

public:
    // 
    // Publically exposed operator()(). 
    // Handles template recursion over parameter pack.
    // Takes the functor to be executed and a parameter 
    // pack of arguments to pass to the functor, one at a time.
    template<typename Functor, typename... Ts>
    void operator()(Functor f, Ts&&... args)
    {
        // 
        // Statically iterate over parameter
        // pack from the first argument to the
        // last, calling functor f with each 
        // argument in the parameter pack.
        internal_static_for<0u, sizeof...(Ts)>()(f, args...);
    }
};

Hoffe die Leute finden das nützlich :-)

pje
quelle