unpack von variadic Parameter für void-Funktionsaufruf(e)
-
Hallo zusammen,
vor einiger Zeit, hat Camper hier ein variadic-Template-Hilfs-Konstrukt gepostet,
das einem ein variadic-Zähler-Parameter zur Verfügung stellt. Generell find ich das Konstrukt echt super, aber jetzt würde ich gerne pro Wert im Vektor eine void-Funktion aufrufen.#include <tuple> #include <iostream> template <unsigned int... T> struct indexes {}; template <typename> struct index_add; template <unsigned int... i> struct index_add<indexes<i...> > { typedef indexes<0, ( i + 1 )...> type; }; template <unsigned int N> struct make_indexes { typedef typename index_add<typename make_indexes<N-1>::type>::type type; }; template <> struct make_indexes<0> { typedef indexes<> type; }; template<typename... T> void dummy(const T&...t){} template<typename T> int print(const T& t){ std::cout << t << std::endl; return 0; } template<unsigned int... I, typename... T> void print_all(indexes<I...>, const std::tuple<T...>& tuple){ dummy(print(std::get<I>(tuple))...); // print(std::get<I>(tuple));... } int main(){ std::tuple<int, double, char> tuple(1, 2.0, 'c'); print_all(make_indexes<3>::type(), tuple); }Solange man in eine andere Funktion oder Konstruktor entpacken kann, ist alles super. Bei einer non-void-Funktion kann man (theoretisch) auch einfach eine Dummy-Funktion schreiben, die eigentlich gar nichts macht...
Aber bei einer void-Funktion sehe ich keine Möglichkeit. Geht es nicht, oder verwende ich eine falsche unpack-Syntax?Also generell kann ich es natürlich so machen:
#include <tuple> #include <iostream> template <unsigned int... T> struct indexes {}; template <typename> struct index_add; template <unsigned int... i> struct index_add<indexes<i...> > { typedef indexes<0, ( i + 1 )...> type; }; template <unsigned int N> struct make_indexes { typedef typename index_add<typename make_indexes<N-1>::type>::type type; }; template <> struct make_indexes<0> { typedef indexes<> type; }; void print(){ } template<typename TH, typename... T> void print(const TH& h, const T&... t){ std::cout << h << std::endl; print(t...); } template<unsigned int... I, typename... T> void print_all(indexes<I...>, const std::tuple<T...>& tuple){ print(std::get<I>(tuple)...); } int main(){ std::tuple<int, double, char> tuple(1, 2.0, 'c'); print_all(make_indexes<3>::type(), tuple); }Gibt es aber nicht einen einfacheren Funktionsaufruf wie im ersten Beispiel angedeutet? Gehe ich richtig in der Annahme, dass die Reihenfolge der print-Aufrufe im 1. Beispiel nicht definiert ist?
Gruß,
XSpille
-
Die Reihenfolge ist unspezifiziert für Funktionsaufrufe, eine Aggregat-Initialisierung bietet sich deshalb an. Mit variablen Funktionstypen kommt man durch kreative Nutzung des Komma-Operators zurecht. z.B.
template<unsigned int... I, typename... T> void print_all(indexes<I...>, const std::tuple<T...>& tuple){ bool dummy[] = { (print(std::get<I>(tuple)),true)... }; }
-
camper schrieb:
Die Reihenfolge ist unspezifiziert für Funktionsaufrufe, eine Aggregat-Initialisierung bietet sich deshalb an. Mit variablen Funktionstypen kommt man durch kreative Nutzung des Komma-Operators zurecht. z.B.
template<unsigned int... I, typename... T> void print_all(indexes<I...>, const std::tuple<T...>& tuple){ bool dummy[] = { (print(std::get<I>(tuple)),true)... }; }Danke dir!

Da wäre ich nie draufgekommen...
Dein "deshalb" verstehe ich allerdings nicht. Ist bei deiner Lösung die Reihenfolge spezifiziert? Also intuitiv würde ich sagen "nein", obwohl es bei meinem Compiler die 'richtige' Reihenfolge ist...
Gruß,
XSpille
-
C++11 schrieb:
8.5.4/4
Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list. [ Note: This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call. —end note ]Diese Regel findet sich auch in C++03: die einzelnen Elemente werden in der Reihenfolge ihres Auftretens initialisiert, und zwischen den einzelnen Initialisierern in der Initialisierungsliste befindet sich ein sequence point.
Das ist im Prinzip ganz analog zu Klassenmembern, die im Konstruktor in der Initialisierungsliste initialisiert werden. Es ist zum Beispiel möglich, auf bereits initialisierte Member zuzugreifen.
Sinnfreies Beispieltemplate <int... i> void foo(indexes<i...>) { int fib[] = { 0, 1, (fib[i]+fib[i+1])... }; };
-
DANKE dir!
