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 if
C ++ (ein Konstrukt aus der Programmiersprache D ) gesehen hatte, hatte ich das Gefühl, dass auch eine Art von Konstrukt static for
nützlich sein würde - und ich denke, dass mehr dieser static
Konstrukte 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_for
wü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:
- keine unnötigen Provisorien zu erstellen , um Argumente zu enthalten, die perfekt an einige generische Algorithmen weitergeleitet werden können (und sollten);
- 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?
foo(args);...
Antworten:
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:
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
print
obige 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 die
forward_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 Helfernfirst_value_of()
undlast_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. Das
is_homogeneous_pack<>
Metafunktion ermöglicht die Bestimmung, ob alle Typen in einem Parameterpaket homogen sind, und soll hauptsächlich instatic_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 von
homogeneous-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.
quelle
static if
undstatic for
fü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.last_type_of
mit 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.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 -O1
undmain
enthält nur 3 Aufrufe anprint
, es gibt keine Spur der Erweiterungshelfer.quelle
int
unddouble
s spielt das keine große Rolle, aber mit UDTs bedeutet dies, dass Kopien und Verschiebungen generiert werden. Und Sie können nichtEXPAND(print(args))
in EXPAND (print (forward <T> (args))) wechseln , da der Makro-Präprozessor etwas Unhöfliches ruft.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;
quelle
enumerate
Beispiel ist etwas, das ich wirklich vermisse, obwohl ich denke, dass es normalerweise mehr wärefor (auto p: enumerate(container))
und ich nicht sicher bin, ob die beiden Versionen (Container-Iteration und Tupel-Iteration) gut zusammenleben würden :)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.
quelle
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 :-)
quelle