Teilen von variadic Parametern in x-gleichgroße Teile
-
Hallo zusammen,
ist es möglich die variadic-Parameter in x-gleichgroße Teile zu splitten?
EDIT: Also identische Anzahl von ElementenAlso schematisch meine ich sowas (hier x=2):
template<typename...T> std::pair<std::tuple<T1...>, std::tuple<T2...> > two_tuples(T&&...t){ return std::make_pair(std::tuple<T1...>(t1), std::tuple<T2...>(t2)); } int main() { two_tuples(1,'c',2.0,2); // {{1,'c'},{2.0,2}} }Generell ist es nur eine theoretische Frage.
Ich möchte halt wissen, was alles mit variadic Templates möglich ist.
In diesem Bespiel könnte man die Tupel mit std::tuple_cat rekursiv aufbauen.
Aber geht es auch ohne den variadic Parameter in ein Tupel umzuwandeln?Gruß,
XSpille
-
Mit std::tupel hab ich mich nie beschäftigt. Wenns dir aber nur konzeptuell darum geht, ob man ein ParameterPack splitten kann:
template< class ...Types > class TypeList { }; //Damit man das Ergebnis nachher überprüfen kann, Spezialisierungen von TypeList template<> class TypeList< int , char > { public: TypeList() { std::cout<< "int , char " <<std::endl; } }; template<> class TypeList< std::string , long > { public: TypeList() { std::cout<< "std:.string , long " <<std::endl; } }; template< class SmallerList , class BiggerList > class Splitter; template< class ...SmallerElements > class Splitter< TypeList< SmallerElements... > , TypeList<> > { public: typedef TypeList<> FirstList; typedef TypeList< SmallerElements... > SecondList; }; template< class BiggerHead , class ...BiggerTail , class ...SmallerElements > class Splitter< TypeList< SmallerElements... > , TypeList< BiggerHead , BiggerTail... > > { public: typedef typename TSL::TypeIfElse < 1 + sizeof...( BiggerTail ) == sizeof...( SmallerElements ) , TypeList< SmallerElements... > , typename Splitter< TypeList< SmallerElements... , BiggerHead > , TypeList< BiggerTail... > >::FirstList >::Value FirstList; typedef typename TSL::TypeIfElse < 1 + sizeof...( BiggerTail ) == sizeof...( SmallerElements ) , TypeList< BiggerHead , BiggerTail... > , typename Splitter< TypeList< SmallerElements... , BiggerHead > , TypeList< BiggerTail... > >::SecondList >::Value SecondList; }; int main() { Splitter< TypeList<> , TypeList< int , char , std::string , long > >::FirstList first; Splitter< TypeList<> , TypeList< int , char , std::string , long > >::SecondList second; std::cin.get(); }Das würde man natürlich noch in einen schöneren Wrapper einbetten (Der nur noch einen einzigen Parameterpack hat), aber ich denke das Konzept ist klar.
-
Ich sehe keine Möglichkeit, das direkt zu machen, allerdings gibt es eine nicht-rekursive Lösung, wenn man das Ganze zunächst in ein tuple packt (vorausgesetzt, get_element wurde nicht-rekursiv implementiert). Ich schreibs morgen mal auf (Idee: baue eine Indexliste mit der halben Länge des Ausgangspacks und expandiere get_element darüber). Indexlisten haben ich kürzlich öfter verwendet, findest du also in meine Beiträgen der letzten Wochen.
get_element kann nicht-rekursiv implementiert werden, g++ macht es vor (wobei ich einigermaßen sicher bin, dass das sogar noch eleganter möglich ist).@GorbGorb: Ich glaube nicht, dass du das gestellte Problem erfasst hast. Templateparameterpacks sind relativ einfach zu handhaben (und rekursive Lösungen stören bei der Ausführung dann nicht). Hier geht es aber um das effiziente Splitten von Funktionsparameterpacks.
-
Doch noch schnell hingeschrieben:
#include <tuple> template <std::size_t...> struct indexes {}; template <typename> struct index_add; template <std::size_t... i> struct index_add<indexes<i...>> { typedef indexes<0, ( i + 1 )...> type; }; template <std::size_t 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 <std::size_t... i, typename... T> auto tuple_split(indexes<i...>, std::tuple<T...> t) -> decltype(std::make_pair(std::forward_as_tuple(std::get<i>(t)...), std::forward_as_tuple(std::get<i+sizeof...i>(t)...))) { return std::make_pair(std::forward_as_tuple(std::get<i>(t)...), std::forward_as_tuple(std::get<i+sizeof...i>(t)...)); } template<typename...T> auto two_tuples(T&&...t) -> decltype(tuple_split(typename make_indexes<(sizeof...t)/2>::type(), std::forward_as_tuple(t...))) { return tuple_split(typename make_indexes<(sizeof...t)/2>::type(), std::forward_as_tuple(t...)); }Die Verallgemeinerung auf n Teiltupel steht noch aus.
-
Ich danke euch!!!

Ich werd mir eure Vorschläge dann morgen mal genau ankucken

-
Ich habs mir doch noch angekuckt ^^
@GorbGorb: Eine Implementierung einer eigenen Klasse ist nicht das, was ich wollte... Dennoch DANKE
@camper:
Ich habs mir angekuckt und bin begeistert von dem Index, den ich gleich mal auf eine vorherige Fragestellung angewandt habe:
http://www.c-plusplus.net/forum/p2110400#2110400Dein(?)
make_indexeswerde ich wohl in meine xsp-Standard-Library (in leicht abgewandelter Form) aufnehmen
-
Wenn ich mich nicht gerade vertue, sollte man das "forward_as_tuple" in tuple_split nach "make_tuple" ändern. Denn sonst bekommst Du ein Tuple, welches ausschließlich Referenzen enthält. Das "forward_as_tuple" in two_tuples dürfte korrekt sein.
-
#include <tuple> #include <utility> #include <type_traits> #define TYPEOF(...) typename std::decay<decltype(__VA_ARGS__)>::type #define AUTO_RETURN(...) ->TYPEOF(__VA_ARGS__) {return __VA_ARGS__;} template<class T> struct id {typedef T type;}; template<int...> struct intpack{}; template<int B, int N, int...Tail> struct make_range : make_range<B,N-1,N-1,Tail...> {}; template<int B, int...Tail> struct make_range<B,B,Tail...> : id<intpack<Tail...>> {}; template<int B, int N> auto make_range_obj() AUTO_RETURN( typename make_range<B,N>::type() ) template<class T, int...Idx> auto make_sub_tuple(T && tuple, intpack<Idx...>) AUTO_RETURN( std::make_tuple( std::get<Idx>(std::forward<T>(tuple))... ) ) template<class...T> auto two_tuples(T&&...args) AUTO_RETURN( std::make_pair( make_sub_tuple( std::tuple<T&&...>(std::forward<T>(args)...), make_range_obj<0,sizeof...(T)/2>() ), make_sub_tuple( std::tuple<T&&...>(std::forward<T>(args)...), make_range_obj<sizeof...(T)/2,sizeof...(T)>() ) ) ) #include <iostream> #include <ostream> #include <type_traits> int main() { using namespace std; auto pair_of_tuples = two_tuples(42,3.14159265,'c',99L); typedef decltype(pair_of_tuples.first) first_type; typedef decltype(pair_of_tuples.second) second_type; static_assert(is_same<first_type, tuple<int,double>>::value,"error"); static_assert(is_same<second_type,tuple<char,long >>::value,"error"); cout << "Fine! :-)" << endl; }
-
XSpille schrieb:
Dein(?)
make_indexeswerde ich wohl in meine xsp-Standard-Library (in leicht abgewandelter Form) aufnehmen
Das variadic Template-Zeug, dass ich hier schreibe, ist so gut wie immer auf meinem eigenen Mist gewachsen. Ich bezweifle allerdings stark, dass ich der erste bin, dem das eingefallen ist.
Hier gibts noch eine effizientere Form.
Eine allgemeine Form für x Teiltupel scheint mir extrem schwierig bis unmöglich zu sein, wenn man keine weitere Indirektion haben will (man braucht zwei Indizes, eins für die Elemente in jedem Teiltuple, und eins für jedes Tupel - so wie die Packexpansionsregeln funktionierten, würden diese aber immer gleichzeitig unter dem ... Operator expandiert werden).
-
Verallgemeinerung auf x Tuple:
#include <tuple> template <typename T> struct identity { typedef T type; }; template <std::size_t... i> struct indexes : identity<indexes<i...>> {}; template <typename... T> struct index_cat : index_cat<typename T::type...> {}; template <std::size_t... i0, std::size_t... i1, typename... T> struct index_cat<indexes<i0...>, indexes<i1...>, T...> : index_cat<indexes<i0..., ( i1 + sizeof... i0 )...>, T...> {}; template <typename T> struct index_cat<T> : T {}; template <typename T> struct index_twice : index_cat<T, T> {}; template <std::size_t N> struct index_make : index_cat<index_twice<index_make<N/2>>, index_make<N%2>> {}; template <> struct index_make<1> : indexes<0> {}; template <> struct index_make<0> : indexes<> {}; template <std::size_t N> constexpr typename index_make<N>::type make_indexes() { return typename index_make<N>::type(); } template <std::size_t N, std::size_t... elem, typename... T> constexpr auto sub_tuple(std::tuple<T...>& t, indexes<elem...>) -> decltype( std::forward_as_tuple( std::get<N * sizeof... elem + elem>( t )... ) ) { return std::forward_as_tuple( std::get<N * sizeof... elem + elem>( t )... ); } template <std::size_t... tuple, std::size_t... elem, typename... T> constexpr auto tuple_split(std::tuple<T...> t, indexes<tuple...>, indexes<elem...>) -> decltype( std::make_tuple( sub_tuple<tuple>( t, indexes<elem...>() )... ) ) { return std::make_tuple( sub_tuple<tuple>( t, indexes<elem...>() )... ); } template<std::size_t N, typename... T> constexpr auto tuple_split(T&&... t) -> decltype( tuple_split( std::forward_as_tuple( t... ), make_indexes<N>(), make_indexes<sizeof... t / N>() ) ) { return tuple_split( std::forward_as_tuple( t... ), make_indexes<N>(), make_indexes<sizeof... t / N>() ); } #include <iostream> int main() { auto x = tuple_split<3>( 'a', 1, 2.0 ); std::cout << std::get<0>(std::get<0>(x)) << '\n' << std::get<0>(std::get<1>(x)) << '\n' << std::get<0>(std::get<2>(x)) << '\n'; }Vorausgesetzt wird noch, dass das Ausgangstuple eine passende Größe hat, das Fallenlassen dieser Bedingung lasse ich mal als Übungsaufgabe dastehen.
-
Leider ist der Typ von
xein tuple<tuple<char**&>,tuple<int&>,tuple<double&**>>. x enthält baumelnde Referenzen. Du hast also wieder forward_as_tuple falsch verwendet.
-
krümelkacker schrieb:
Leider ist der Typ von
xein tuple<tuple<char**&>,tuple<int&>,tuple<double&**>>. x enthält baumelnde Referenzen. Du hast also wieder forward_as_tuple falsch verwendet.Der Hinweis auf den Fehler ist in Ordnung, der Rest weniger.
Dass Refernzen auftauchen, war durchaus beabsichtigt, allerdings sind es offenbar die Falschen, und darauf gehst du gar nicht ein.
Es ist nicht einfach eine Frage von make vs. forward.
Abgesehen davon ist es wahrscheinlich zweckmäßig, sowohl Referenz- als auch Value-Splits anzubieten:template <typename T> struct tuple_decay_ : identity<T> {}; template <typename T> struct tuple_decay : tuple_decay_<typename std::decay<T>::type> {}; template <typename... T> struct tuple_decay_<std::tuple<T...>> : identity<std::tuple<typename tuple_decay<T>::type...>> {}; template <typename T> typename tuple_decay<T>::type decay_tuple(T t) { return t; } template <std::size_t N, std::size_t... elem, typename... T> auto forward_as_subtuple(std::tuple<T...> t, indexes<elem...>) -> decltype( std::forward_as_tuple( std::forward<typename std::tuple_element<N * sizeof... elem + elem, std::tuple<T...>>::type>( std::get<N * sizeof... elem + elem>( t ) )... ) ) { return std::forward_as_tuple( std::forward<typename std::tuple_element<N * sizeof... elem + elem, std::tuple<T...>>::type>( std::get<N * sizeof... elem + elem>( t ) )... ); // das ist eigentlich schon wieder zu barock } template <std::size_t... tuple, std::size_t... elem, typename... T> auto forward_as_split_tuple(std::tuple<T...> t, indexes<tuple...>, indexes<elem...>) -> decltype( std::make_tuple( forward_as_subtuple<tuple>( t, indexes<elem...>() )... ) ) { return std::make_tuple( forward_as_subtuple<tuple>( t, indexes<elem...>() )... ); } template<std::size_t N, typename... T> auto forward_as_split_tuple(T&&... t) -> decltype( forward_as_split_tuple( std::forward_as_tuple( std::forward<T>( t )... ), make_indexes<N>(), make_indexes<sizeof... t / N>() ) ) { return forward_as_split_tuple( std::forward_as_tuple( std::forward<T>( t )... ), make_indexes<N>(), make_indexes<sizeof... t / N>() ); } template<std::size_t N, typename... T> auto make_split_tuple(T&&... t) -> decltype( decay_tuple( forward_as_split_tuple<N>( std::forward<T>( t )... ) ) ) { return decay_tuple( forward_as_split_tuple<N>( std::forward<T>( t )... ) ); }Edit: Wenn ichs mir recht überlege, ist das so herum keine gute Lösung, denn damit können value-splits nicht constexpr sein. Läuft also wahrscheinlich auf doppelten Code heraus. Hat aber eigentlich auch nichts mehr mit dem ursprünglichen Problem zu tun; wenn jemand das in eine Bibliothek aufnehmen will, ist immer noch Zeit, sich darüber Gedanken zu machen.
-
krümelkacker schrieb:
Leider ist der Typ von
xein tuple<tuple<char**&>,tuple<int&>,tuple<double&**>>. x enthält baumelnde Referenzen.Echt?
Ich hätte ehrlich gesagt zu
tuple<tuple<char**&&>,tuple<int&&>,tuple<double&&**>>
tendiert, was allerdings an deinem Einwand nichts ändert...
-
camper schrieb:
krümelkacker schrieb:
Leider ist der Typ von
xein tuple<tuple<char**&>,tuple<int&>,tuple<double&**>>. x enthält baumelnde Referenzen. Du hast also wieder forward_as_tuple falsch verwendet.Der Hinweis auf den Fehler ist in Ordnung, der Rest weniger.
Beleidigt? Nun, ich hatte auf den Fehler vorher schonmal aufmerksam gemacht. Das war wohl nicht deutlich genug. :p
Wenn Du mit tuple_decay das Verhalten von make_tuple emulieren willst, hast Du die reference_wrapper-Sonderbehandlung vergessen. tuple_decay hättest Du über
template<class Tuple> struct tuple_decay; template<class...T> struct tuple_decay<tuple<T...>> : identity< decltype(make_tuple(declval<T>()...)) > {};definieren können.
-
krümelkacker schrieb:
camper schrieb:
krümelkacker schrieb:
Leider ist der Typ von
xein tuple<tuple<char**&>,tuple<int&>,tuple<double&**>>. x enthält baumelnde Referenzen. Du hast also wieder forward_as_tuple falsch verwendet.Der Hinweis auf den Fehler ist in Ordnung, der Rest weniger.
Beleidigt?
Wolltest du provozieren?
krümelkacker schrieb:
Nun, ich hatte auf den Fehler vorher schonmal aufmerksam gemacht. Das war wohl nicht deutlich genug. :p
Meine Erwiderung offenbar auch nicht. Das Ergebnis in Form mehrerer Referenztupel war beabsichtigt. forward_as_tuple wurde falsch verwedet, aber in einer Weise, die dir entgangen ist (auch schon im ursprüglichen Code).
krümelkacker schrieb:
Wenn Du mit tuple_decay das Verhalten von make_tuple emulieren willst, hast Du die reference_wrapper-Sonderbehandlung vergessen. tuple_decay hättest Du über
template<class Tuple> struct tuple_decay; template<class...T> struct tuple_decay<tuple<T...>> : identity< decltype(make_tuple(declval<T>()...)) > {};Sehe ich zum ersten mal, schau ich mir noch mal an. Mein tuple_decay ist noch zu aggresiv, weil auch Referenztuple, die als Argumente übergeben wurden, aufgelöst werden.
Gemessen daran, dass es sich weitgehend um Neuland für viele von uns handelt, ist es eher zuviel verlangt, perfekte Lösungen zu erwarten. Insofern wäre es vermutlich produktiver, wo möglich, konkrete Vorschläge zu machen, als bloß auf Fehler hinzuweisen.
Ich sehe allerdings nicht, wie du micht beledigt haben könntest.XSpille schrieb:
Ich hätte ehrlich gesagt zu
tuple<tuple<char**&&>,tuple<int&&>,tuple<double&&**>>
tendiert, was allerdings an deinem Einwand nichts ändert...
So war es beabsichtigt. Allerdings ist es in
forward_as_tuple( t... )zu spät für rvalues. Folglich
forward_as_tuple( std::forward<T>( t )... )Eigentlich sollte
forward_as_tuple<T...>( t... )auch funktionieren, aber der Compiler schluckt es nicht. Wahrscheinlich ist da noch einen Denkfehler drin.
-
camper schrieb:
Wolltest du provozieren?
Nicht wirklich.
camper schrieb:
...aber in einer Weise, die dir entgangen ist (auch schon im ursprüglichen Code).
Woher willst Du das wissen? Ich habe nie behauptet, dass jede Verwendung von forward_as_tuple falsch war, sondern mich nur auf eine Stelle bezogen.
camper schrieb:
Gemessen daran, dass es sich weitgehend um Neuland für viele von uns handelt, ist es eher zuviel verlangt, perfekte Lösungen zu erwarten. Insofern wäre es vermutlich produktiver, wo möglich, konkrete Vorschläge zu machen, als bloß auf Fehler hinzuweisen.
Habe ich getan (für die 2-tuple-Version). Direkt nach dem Hinweis.
Ich hätte auch forward_as_tuple verwendet. Aber das kann meine Version des GCCs noch nicht. Stattdessen sieht man da
tuple<T&&...>(forward<T>(args)...)irgendwo; denn nichts weiter machtforward_as_tuple(forward<T>(args)...).camper schrieb:
Ich sehe allerdings nicht, wie du micht beledigt haben könntest.
Dann ist ja gut.
-
krümelkacker schrieb:
Wenn Du mit tuple_decay das Verhalten von make_tuple emulieren willst, hast Du die reference_wrapper-Sonderbehandlung vergessen. tuple_decay hättest Du über
template<class Tuple> struct tuple_decay; template<class...T> struct tuple_decay<tuple<T...>> : identity< decltype(make_tuple(declval<T>()...)) > {};definieren können.
Habe mir das jetzt noch mal angeschaut. Soweit ich sehe löst das das Problem nicht (weil ja nicht nur die Elemente des Top-Level-Tupels umgewandelt werden müssen, sondern die Referenzen, die in den einzelnen Tupelelementen stecken).
Ich halte es auch nicht für einfacher - wenn schon eine Typliste vorliegt, halte ich es für simpler, darauf eine Metafunktion direkt anzuwenden, als erst eine Ausdrucksliste zu erzeugen, die dann nach entsprechender Verwurstung per declval wieder zurückverwandelt werden muss. Prinzipiell sind ja beide Wege äquivalent.Korrekt müsste eine N-stufige Umwandlung sein, der Art:
template <std::size_t N, typename T> struct tuple_decay : std::decay<T> {}; template <std::size_t N, typename... T> struct tuple_decay<N, std::tuple<T...>> : identity<std::tuple<typename tuple_decay<N - 1, T>::type...>> {}; template <typename T> struct tuple_decay<0, T> : std::decay<T> {}; template <typename... T> struct tuple_decay<0, std::tuple<T...>> : identity<std::tuple<T...>> {}; template <std::size_t N, typename T> typename tuple_decay<N, T>::type decay_tuple(T t) { return t; } template<std::size_t N, typename... T> auto make_split_tuple(T&&... t) -> decltype( decay_tuple<2>( forward_as_split_tuple<N>( std::forward<T>( t )... ) ) ) { return decay_tuple<2>( forward_as_split_tuple<N>( std::forward<T>( t )... ) ); }(Die Verallgemeinerung auf N, falls jemand in Tupel aus Tupeln aus Tupeln ... aufteilen will.
Irgenwann kommen sicherlich auch noch vermehrt interessante Fragen im Forum zu rvalues/forwarding&co. Spätestens dann muss ich mich mehr damit beschäftigen um antworten zu können. Dann wird auch der geschriebene Code besser werden

Gegenwärtig weiß ich das alles nur, aber das intuitive Verstädnis fehlt noch.
-
camper schrieb:
krümelkacker schrieb:
Wenn Du mit tuple_decay das Verhalten von make_tuple emulieren willst, hast Du die reference_wrapper-Sonderbehandlung vergessen. tuple_decay hättest Du über
template<class Tuple> struct tuple_decay; template<class...T> struct tuple_decay<tuple<T...>> : identity< decltype(make_tuple(declval<T>()...)) > {};definieren können.
Habe mir das jetzt noch mal angeschaut. Soweit ich sehe löst das das Problem nicht (weil ja nicht nur die Elemente des Top-Level-Tupels umgewandelt werden müssen, sondern die Referenzen, die in den einzelnen Tupelelementen stecken).
Ich halte es auch nicht für einfacher - wenn schon eine Typliste vorliegt, halte ich es für simpler, darauf eine Metafunktion direkt anzuwenden, als erst eine Ausdrucksliste zu erzeugen, die dann nach entsprechender Verwurstung per declval wieder zurückverwandelt werden muss. Prinzipiell sind ja beide Wege äquivalent.Ich bin ohne so etwas wie tuple_decay ausgekommen. Aber so, wie ich es oben hingeschrieben habe, macht es folgendes:
tuple<const int> --> tuple<int>
tuple<int&> --> tuple<int>
tuple<int&&> --> tuple<int>
tuple<reference_wrapper<int>> --> tuple<int&>Ich habe auch darauf geachtet, nichts unnötig zu kopieren. Move-Constructions werden da u.U. auch genommen. Das habe ich allerdings nicht mehr getestet.
Ich frage mich immer noch, was bei Dir ein std::decay zu suchen hat. Meiner Meinung nach sollte man sich bei dieser Typtransformation auf make_tuple verlassen; denn da steckt schon der decay-plus-reference-wrapper-Kram drin.