Teilen von variadic Parametern in x-gleichgroße Teile
-
#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.