Und mal wieder variadische Templates...
-
höhenkoller schrieb:
std::get muss noch überladen werden
template <int N, typename... T> auto get(T&&... args) { return (make_indices<N>::pack, std::forward<T>(args))...; }
-
höhenkoller schrieb:
Wenn
Args... args
lokal erlaubt ist, sollte das auch als Rückgabetyp erlaubt sein, dann hätten wir ein echtes Built-In-Tupel (std::get muss noch überladen werden). Ach, ich wäre schon überglücklich, wenn auch nur ein Teil der Features übernommen würde.Habe mir überlegt, dass das (bezogen auf std::tuple) redundant ist, wenn man dieser Form nicht eine andere Initialisierungssemantik gibt.
Evtl. könnte man in betracht ziehen, dass die initialisierenden Elemente erst bei der Expansion ausgewertet werden (wie ein Makro)
template <typename... T> void foo(T&&... args) { T&... a = ++args...; cout << ( a << ' ' ) << ... << '\n'; cout << ( a << ' ' ) << ... << '\n'; } foo(1,2,3);
gibt 2 mal 2 3 4 aus, wenn die Auswertung bereits bei der Definition von a erfolgt.
Andernfalls2 3 4 3 4 5
Die erste Variante könnte man aber auch einfach mit einem std::tuple bekommen.
-
camper schrieb:
Habe mir überlegt, dass das (bezogen auf std::tuple) redundant ist, wenn man dieser Form nicht eine andere Initialisierungssemantik gibt.
Das hört sich an, als wolltest du die Semantik um der Redundanz willen ändern. Ich finde das Verhalten unintuitiv.
Lazy kann übrigens auch durch eine Wrapperklasse erreichen, kein Grund, das in die Sprache aufzunehmen:lazy<T>... a = ++make_lazy(args)...; cout << ( a << ' ' ) << ... << '\n'; cout << ( a << ' ' ) << ... << '\n';
Eine nutzbare Syntax für std::tuple finde ich hingegen schon ein guter Grund. Es gibt dieses int_seq-Proposal, aber wirklich Spass macht Tupelprogrammierung damit immer noch nicht.
-
höhenkoller schrieb:
lazy<T>... a = ++make_lazy(args)...; cout << ( a << ' ' ) << ... << '\n'; cout << ( a << ' ' ) << ... << '\n';
Mit Expressiontemplates?
höhenkoller schrieb:
Es gibt dieses int_seq-Proposal, aber wirklich Spass macht Tupelprogrammierung damit immer noch nicht.
N3493? Scheint nur standardisieren, was sowieso schon gemacht wird.
template <typename... T> void foo(T&&... args) { auto a = make_tuple( ++args... ); cout << ( std::get<a.indexes>(a) << ' ' ) << ... << '\n'; // tuple müsste entsprechend erweitert werden }
sehe ich nicht als besonders umständlich an
Ich bin ein bisschen unsicher, ob frühzeitige Auswertung ggf. ineffizient ist, gerade dann, wenn es für forwarding-Zwecke benutzt werden soll. Denn dann kommt die Implementation kaum darum herum, Diese Tuple wie echte Objekte zu implementieren (mit dem damit verbundenen Kopieraufwand).
-
Die Idee mit lazy Auswertung ist wahrscheinlich keine Gute, oder hat jedenfalls nichts speziell mit Packs zu tun.
Es gibt noch ein anderes Problem, das zu lösen wäre
template <typename... T> struct foo { static T... data; }; using... list = foo<int>, foo<char, float, double>; list::data + ...; // ???
Wenn Packs auch ausserhalb von Template- und Funktionsargumenten auftreten können, können sie nat. auch Member einer Klasse sein. Und wenn diese Klasse selbst in einer Liste auftaucht, funktioniert die einfache Regel, dass alle nicht expandierten Packs gleichzeitig expandiert werden, offenbar nicht mehr. Zudem ergibt sich ein syntaktisches Problem innerhalb von Templates, der Compiler müsste wissen, dass es sich bei einem verschachtelten Bezeichner um ein Pack handelt, man braucht also die Möglichkeit, so diese zu kennzeichnen, so wie das bei Membertemplates der Fall ist.
-
camper schrieb:
Mit Expressiontemplates?
Ähnlich zu Boost.Lambda.
auto&&... a = freeze(++make_lazy(args))...;
ohne Deduzieren von Konstruktortemplateargumenten. Ist nicht 100% das gleiche (args + (std::cout << 'x')), dafür muss man dann einen Umweg überlazy_apply(args, [](auto x){...})...
gehen. Hat ausserdem syntaktische Unterschiede, solange sich der Punkt-Operator nicht überladen lässt (da habe ich auch noch kein Proposal gesehen). Aber möglich ist es schon.funktioniert die einfache Regel, dass alle nicht expandierten Packs gleichzeitig expandiert werden, offenbar nicht mehr.
Das ist doch gewollt, damit bekommen wir die Lazy-Expandierung:
template <int X, int Y, int Z> matrix<X, Z> operator*(matrix<X,Y> a, matrix<Y,Z>) { int... ... ... x_list = ((make_indexes<X>::pack>)); int... ... y_list = (make_indexes<y>::pack); int... z_list = make_indexes<z>::pack; return matrix<X, Z>{ a.m[x_list][y_list]*b.m[y_list][z_list]+... ... ... }; }
Die Regel ist dann, dass immer alle äusserste Packs expandieren.
Wäre noch zu überlegen, was das für Auswirkungen hat, Templates ohne
template<...>
schreiben zu können.int... foo() { return <1, 2>; } // ?? foo<char, float>::data bar(); // ?? typedef decltype(list::data) typedefed_pack; // ??
Ich tendiere dazu, das erste zu verbieten (Länge müsste Teil der Signatur sein), beim Rest bin ich noch unsicher.
-
Vorschlag für das Problem:
erlaube die Verschachtelung von Ellipsen (so wie mein lazy-Op-Vorschlag, der damit wahrscheinlich sogar überflüssig wird)template <int... i> struct foo { static constexpr int pack = i...; }; using... foos = foo<0,1>, foo<2,3>; foos::pack ... ...
Dann gibt es im Prinzip 2 Möglichkeit:
Variante 1. expandiere alle Ellipsen, die nicht selbst Teil eines Expansionsmusters sind. Wiederhole bis alle Ellipsen expandiert wurden "von außen nach innen"
1. Schrittfoo<0,1>::pack ... , foo<2,3>::pack ...
2. Schritt
foo<0,1>::pack0, foo<0,1>::pack1, foo<2,3>::pack0, foo<2,3>::pack1
also0, 1, 2, 3
Variante 2. expandiere alle Ellipsen, die keine Ellipse in ihrem Expansionsmuster haben. Wiederhole bis alle Ellipsen expandiert wurden. "von innen nach aussen"
1. Schritt( foo<0,1>::pack , foo<2,3>::pack ) ...
(die Klammern nur gedacht)
2. Schritt
foo<0,1>::pack0, foo<2,3>::pack0, foo<0,1>::pack1, foo<2,3>::pack1
also0, 2, 1, 3
Ich würde Variante 1 bevorzugen, aber evtl. übersehe ich noch etwas.
Edit: da war ich ein bisschen langsam beim Schreiben, hatte den vorherigen Beitrag noch nicht gesehen...
-
höhenkoller schrieb:
template <int X, int Y, int Z> matrix<X, Z> operator*(matrix<X,Y> a, matrix<Y,Z>) { int... ... ... x_list = ((make_indexes<X>::pack>)); int... ... y_list = (make_indexes<y>::pack); int... z_list = make_indexes<z>::pack; return matrix<X, Z>{ a.m[x_list][y_list]*b.m[y_list][z_list]+... ... ... }; }
Mit der Syntax kann ich mich nicht anfreunden. Ist auch nicht erforderlich, wenn wir ein Hilfstemplate einführen:
template <typename... T> struct pack_identity { using... types = T...; }; template <int X, int Y, int Z> matrix<X, Z> operator*(matrix<X,Y> a, matrix<Y,Z>) { using... x_list = pack_identity<make_indexes<X>>; int... y_list = make_indexes<y>::pack...; using... z_list = make_indexes<z>; return matrix<X, Z>{ a.m[x_list::types::pack][y_list]*b.m[y_list][z_list::pack]+... ... ... }; }
höhenkoller schrieb:
Wäre noch zu überlegen, was das für Auswirkungen hat, Templates ohne
template<...>
schreiben zu können.int... foo() { return <1, 2>; } // ?? foo<char, float>::data bar(); // ?? typedef decltype(list::data) typedefed_pack; // ??
Ich tendiere dazu, das erste zu verbieten (Länge müsste Teil der Signatur sein), beim Rest bin ich noch unsicher.
Das habe ich nicht auf der Agenda. ich betrache ... als Deklarator, der nur direkt top-level eingesetzt werden kann.
int... foo();
deklariert dann ein Pack aus FUnktionen, was ich verbieten würde. Ich sehe hier auch keinen Grund, nicht std::tuple einzusetzen.
-
Damit Variante 1 möglich ist, braucht es ein paar Zusatzregeln.
using... a = int, char, long; using... b = float, double; using... c = pair<a, b>...; c == pair<int,float>, pair<char,double> // sonst inkonsistent
int... a = 1, 2, 3; int... b = 4, 5; using... c = matrix<a, b>...; c == matrix<1, 4>, pair<2, 5> // sonst inkonsistent
int... a = 1, 2, 3; int... b = 4, 5; int c = (a + b) *...; c == (1+4) * (2+5) // sonst inkonsistent
int... a = foo<1,2>::pack; int... b = foo<3,4,5>::pack; using... c = matrix<a,b>... ... c == matrix<1,3>, matrix<2,4>
int... a = foo<1,2>::pack; int... b = foo<3,4,5>::pack; using... ab = a, b; // Pseudo-Syntax int... c = identity<ab...>... // Eher 1,3,2,4 int... c = (ab...) ... // Eher 1,3,2,4 int... c = ab... ... // Kaum 1,2,3,4,5
-
Wie diese Beispiele zu behandlen sind, legt bereits der jetzige Standard fest. Dein Vorschlag würde existierenden Code verändern.
-
Mein Vorschlag? Es ging mir darum, das Verhalten für
template <int... i> struct foo { static constexpr int pack = i...; }; using... foos = foo<0,1>, foo<2,3>; foos::pack ... ...
abzuleiten, und das sieht halt stark nach
0,2,1,3
aus.
-
Habe noch einmal etwas nachgedacht.
int... foo()
wäre nat. eine Funktion, die ein Pack zurückgibt. Ich würde so etwas entweder ganz verbieten, oder, wenn wie hier die Ellipse nicht auf ein Pack einwirken kann, dies so interpretieren, dass das Pack aus einem einzelnen Element bestehen soll.
Bei Referenz-/Objektdefinitionen können wir einfach verlangen, dass stehts eine Initialisiererlist angegeben werden muss, aus der dann die Anzahl bestimmt werden kann.
Die Möglichkeit Packs zurückzugeben, ist eigentlich weitgehend redundant. Mir fällt eigentlich nur ein nützlicher Fall ein, um damit auf eine spezielle Syntax zum Deklarieren von anonymen Packs verzichten zu können.
template <typename... T> constexpr T&&... value_pack(T&&... args) { return... std::forward<T>(args)...; // oder ggf. mit einfachem return, falls hier die Syntax kontextabhängig modifiziert wird } template <typename... T> struct identity { using... types = T...; }; template <typename T> struct identity { using type = T; using... types = T; }; // pack aus Werten: value_pack(1,2,3) // pack aus Typen identity<char,int,double>::types
using... a = int, char, long; using... b = float, double; using... c = pair<a, b>...; c == pair<int,float>, pair<char,double> // sonst inkonsistent
ill-formed
int... a = 1, 2, 3; int... b = 4, 5; using... c = matrix<a, b>...; c == matrix<1, 4>, pair<2, 5> // sonst inkonsistent
ill-formed
int... a = 1, 2, 3; int... b = 4, 5; int c = (a + b) *...; c == (1+4) * (2+5) // sonst inkonsistent
ill-formed
int... a = foo<1,2>::pack; int... b = foo<3,4,5>::pack; using... c = matrix<a,b>... ... c == matrix<1,3>, matrix<2,4>
ill-formed (schon unter der ersten Ellipse)
int... a = foo<1,2>::pack; int... b = foo<3,4,5>::pack; using... ab = a, b; // Pseudo-Syntax int... c = identity<ab...>... // Eher 1,3,2,4 int... c = (ab...) ... // Eher 1,3,2,4 int... c = ab... ... // Kaum 1,2,3,4,5
ebenfalls ill-formed
in keinen von deinen Beispielen tritt die Form pack::nested_pack auf.