Und mal wieder variadische Templates...
-
Hi,
langsam komme ich mir wie ein Depp vor, jedes mal wenn ich etwas mit variadischen Templates mache, hier nen Thread aufzumachen...
Aber so sei es:template<typename... ArgTypes> void pushArgs(lua_State* L, ArgTypes const&... args) { StackOps<ArgTypes>::push(L, args)...; }
Wo ist denn hier das Problem?
Fehler: expected »;« before »...« token| Fehler: Parameterbündel nicht mit »...« aufgelöst:|
Danke schonmal & Grüße,
Ethon
-
So geht es nicht. Ich mach es mal ohne Umschweife, geht es so?
template<typename... ArgTypes> void pushArgs(lua_State* L, ArgTypes const&... args) { char arr[] = { (StackOps<ArgTypes>::push(L, args), '\0')... }; }
Sonst gibt es natürlich auch andere Wege.
arr
wird aber natürlich wegoptimiert.Das Problem ist, dass deine pack-expansion in einem ungültigen Kontext steht. Es muss laut [temp.variadic]/4 eines der folgenden sein:
Pack expansions can occur in the following contexts:
— In a function parameter pack (8.3.5); the pattern is the parameter-declaration without the ellipsis.
— In a template parameter pack that is a pack expansion (14.1):
— if the template parameter pack is a parameter-declaration; the pattern is the parameter-declaration
without the ellipsis;
— if the template parameter pack is a type-parameter with a template-parameter-list; the pattern is
the corresponding type-parameter without the ellipsis.
— In an initializer-list (8.5); the pattern is an initializer-clause.
— In a base-specifier-list (Clause 10); the pattern is a base-specifier.
— In a mem-initializer-list (12.6.2); the pattern is a mem-initializer.
— In a template-argument-list (14.3); the pattern is a template-argument.
— In a dynamic-exception-specification (15.4); the pattern is a type-id.
— In an attribute-list (7.6.1); the pattern is an attribute.
— In an alignment-specifier (7.6.2); the pattern is the alignment-specifier without the ellipsis.
— In a capture-list (5.1.2); the pattern is a capture.
— In a sizeof... expression (5.3.3); the pattern is an identifier.
-
Ja, so passt es, danke.
Habe mir soetwas schon gedacht, da ich die pack-expansions in letzter Zeit nur mit make_tuple genutzt habe, da war es eine andere Benutzung.Finde es recht überflüssig dass so ein Hack notwendig ist, so ungewöhnlich ist diese Verwendung nicht.
-
Finde es recht überflüssig dass so ein Hack notwendig ist, so ungewöhnlich ist diese Verwendung nicht.
Ist sie auch nicht. Aber die Logik von variadischen Templates ist nun mal so. Wenn du auf die einzelnen Elemente eines parameter-packs zugreifen willst, kannst du auch rekursiv vorgehen, oder eben über eine pack-expansion.
Wenn es dich stört, bastel dir ein Makro. Mach es aber so, dass das Array in einem inneren Scope deklariert wird, damit keine Namenskonflikte entstehen.
-
Vielleicht schlägt ja mal eine eine Erweiterung vor, um Packexpansionen als einfachen Ausdruck verwenden zu können
i + ... // wird zu i0 + i1 + i2 usw. i * ... // wird i0 * i1 * i2 usw.
Das könnte im Prinzip mit jedem binären Operator funktionieren, wobei ein paar Probleme zu klären wären (z.B. Behandlung von leeren Parameterpacks; Syntax für umgekehrte Assoziativität i0/(i1/i2) statt (i0/i1)/i2 ...)
-
z.B. Behandlung von leeren Parameterpacks; Syntax für umgekehrte Assoziativität i0/(i1/i2) statt (i0/i1)/i2 ...
Kettenbrüche? Das ist dann doch etwas unintuitiv. Ich schlage vor, nur + und * zu nutzen. Da entsteht auch kein Problem mit der Assoziativität, da ein solcher Ausdruck kommutativ ist. Und leere Parameterpacks sind entweder
- ill-formed. Macht keinen Sinn.
- (i * ...) ist, wenn i leer ist, 1; und (i + ...) ist 0.
Ich verstehe aber das Problem nicht. Man schafft das schon ganz leicht, egal ob für template- oder function-parameter-packs:
#include <iostream> template< typename T, T lhs, T rhs > struct static_plus { static constexpr T value = lhs + rhs; }; template< typename T, template< typename T1, T lhs, T rhs > class action, T first, T ... tail > struct perform { static constexpr T value = first + perform<T, action, tail...>::value; }; template< typename T, template< typename T1, T lhs, T rhs > class action, T first > struct perform<T, action, first> { static constexpr T value = first; }; #include <algorithm> template<typename ... T> using common = typename std::common_type<T...>::type; template<typename FirstT, typename ... T> common<FirstT, T...> add( FirstT first, T ... args ) { typedef common<FirstT, T...> rvalT; rvalT arr[] = { rvalT(args)... }; return std::accumulate( arr, arr + sizeof...(args), rvalT(first) ); } int main() { std::cout << perform<int, static_plus, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10>::value; std::cout << add( 1, 5, 9, 7.4, 5.76f ); }
-
Sone schrieb:
z.B. Behandlung von leeren Parameterpacks; Syntax für umgekehrte Assoziativität i0/(i1/i2) statt (i0/i1)/i2 ...
Ich schlage vor, nur + und * zu nutzen.
Das wäre völlig willkürlich. Ich bin 100% gegen neues Features, die nicht wenigsten versuchen, einigermaßen orthogonal zum Rest der Sprache zu sein.
Sone schrieb:
Da entsteht auch kein Problem mit der Assoziativität, da ein solcher Ausdruck kommutativ ist.
Woher weisst du, dass mein überladener Operator kommutativ ist? Ich habe auch nichts speziell mit / vor, dass diente nur der Illustration.
Sone schrieb:
Und leere Parameterpacks sind entweder
- ill-formed. Macht keinen Sinn.
- (i * ...) ist, wenn i leer ist, 1; und (i + ...) ist 0.
ill-formed ist sicher eine Möglichkeit, aber recht unflexibel. Die zweite Möglichkeit ist recht willkürlich (und der Typ ist auch nicht so klar - i ist ja im allgemeinen nicht einfach nur ein Element eines Packs, für diesen langweiligen Fall brauchen wir keine Spracherweiterung).
Sone schrieb:
Ich verstehe aber das Problem nicht. Man schafft das schon ganz leicht, egal ob für template- oder function-parameter-packs:
Ja, das geht nat. (jedenfalls wenn man die Domäne des Problems hinreichend einschränkt). Es geht mir auch gar nicht darum, Probleme zu lösen, für die es bis jetzt keine Lösung gibt.
-
Ups, na dass du jeden Typ mit ggf. überladenen Operatoren berücksichtigen willst, war mir nicht klar.
Die Willkürlichkeit bekommste in den falschen Hals. Ich habe das - so engstirnig ich nun bin - erstmal nur für skalare Typen genommen. Da war das gar nicht willkürlich. Potenzen, deren Exponent Null ist, sind auch 1. Und wenn das als Faktor auftreten soll, finde ich das auch nützlicher.
Allerdings hast du schon Recht, dass soll orthogonal zum Rest der Sprache verlaufen - und damit auch für jeden Operator und jeden Typ verfügbar sein.
Ich finde es trotzdem unnötig. Was soll das bringen? Es sieht schön einfach aus, aber nur auf den ersten Blick. Sobald die von dir genannten Aspekte ins Spiel kommen, wird eine Flexibilität erzwungen, die die Einfachheit der Sache verdrängt.
Woher weisst du, dass mein überladener Operator kommutativ ist?
Woher soll ich wissen, dass du Matrizen mithilfe von variadic-templates multiplizieren willst?
Neh, aber mal im Ernst. Wenn du deine Multiplikation o. ä. geregelt ablaufen lassen möchtest, schreibe dir eine Funktion dafür. Solche Sprachfeatures sind eben deswegen nicht existent, weil sie sehr vielseitig funktionieren können, und sich keiner auf eine Funktionsweise einigen kann. Wie willst du die Assoziativität definieren?
Natürlich könnte man das weiter treiben und die Syntax so erweitern, dass man das irgendwie hineingeschustert bekommt. Aber dann müllt man die Sprache wieder mit solchem Krimskrams zu.
-
i ist ja im allgemeinen nicht einfach nur ein Element eines Packs
Ich dachte, i ist das pack (?)
-
...
-
--
-
Kellerautomat schrieb:
camper schrieb:
Vielleicht schlägt ja mal eine eine Erweiterung vor, um Packexpansionen als einfachen Ausdruck verwenden zu können
i + ... // wird zu i0 + i1 + i2 usw. i * ... // wird i0 * i1 * i2 usw.
Das könnte im Prinzip mit jedem binären Operator funktionieren, wobei ein paar Probleme zu klären wären (z.B. Behandlung von leeren Parameterpacks; Syntax für umgekehrte Assoziativität i0/(i1/i2) statt (i0/i1)/i2 ...)
Und was soll dieser Ausdruck darstellen?
Was meinst du? Wie er definiert ist, hat er ja gezeigt.
Edit: Zu spät, Kellerautomat, QFT
-
Als motivierendes Beispiel kann durchaus der Originalcode dienen
template<typename... ArgTypes> void pushArgs(lua_State* L, ArgTypes const&... args) { StackOps<ArgTypes>::push(L, args) , ...; // Kommaoperator }
halte ich für wesentlich schöner und weniger subtil als eine Arrayinitialisierung.
Ich tendiere auch dazu, leere Parameterpacks zuzulassen, das Ergebnis ist dann eben ein nichts was ja z.B. auch in diesem Code nicht per se Unsinn wäre.
Evtl. verfeinert man das noch mit ein Kollapsregel, wenn der gleiche Operator, der für die Packexpansion genutzt wird, unmittelbar zuvor oder danach auftritt:// typelist T0,T1,T2... auto serial_size = 0 + sizeof(T) + ... // wird zu = 0 wenn die Liste leer ist
-
camper schrieb:
StackOps<ArgTypes>::push(L, args) , ...; // Kommaoperator
Das finde ich sehr schön. Allerdings gibt man damit die Garantie auf, dass die Auswertung sequenced ist (der Kommaoperator kann überladen werden). Also müsste man den Code zu dem hier ändern:
(void)StackOps<ArgTypes>::push(L, args) , ...; // Built-In Kommaoperator
Besser wäre, wenn der Compiler das automatisch macht. Dann gibt es jedoch keine Möglichkeit mehr, den überladenen Kommaoperator zu verwenden. Ich wäre deshalb für
StackOps<ArgTypes>::push(L, args)...; // "Semikolon-Operator"
Allerdings ist der Unterschied zu deiner Version wieder recht subtil.
camper schrieb:
Evtl. verfeinert man das noch mit ein Kollapsregel, wenn der gleiche Operator, der für die Packexpansion genutzt wird, unmittelbar zuvor oder danach auftritt:
Was ist mit anderen Operatoren in der Nähe?
auto serial_size = 0 + (1 * sizeof(T)) + ... // erlaubt? auto serial_size = 0 + 1 * sizeof(T) + ... // illformed oder wie Zeile oben? auto custom_sizeof = 0.0 + sizeof(T) + ... // "(((0.0 + sizeof(T0)) + sizeof(T1)) + ..." // oder "0.0 + (sizeof(T0) + sizeof(T1) + ...)"? auto custom_sizeof = sizeof...(T)==0 ? 0 : sizeof(T) + ...; // ill-formed? std::cout << args << ... << '\n'; // vermutlich std::cout << (args << ...) << '\n' und std::cout << '\n'
Wenn hier schon Wunschkonzert ist, das würde ich mir auch noch wünschen:
template <typename T, T... I> index_list { typedef... T indices; // typedef auf Variadische Liste von ints }; template <std::size_t N, typename T=int> using int_seq = make_indices<T, N>::indices; auto wighted_sum = 0 + (int_seq<sizeof...(T)> * sizeof(T))...; // Liste kann hier entpackt werden.
-
std::cout << "{ " << (args << ' ' <<)... << "}\n"; // Geht sowas ohne Workaround?
-
höhenkoller schrieb:
std::cout << "{ " << (args << ' ' <<)... << "}\n"; // Geht sowas ohne Workaround?
Ja.
std::cout << '{'; char arr[]{ ( std::cout << args << ' ', '\0' ) ... };
Ungetestet, sollte aber genau so gehen.
-
Sone schrieb:
char arr[]{ ( std::cout << args << ' ', '\0' ) ... };
Ungetestet, sollte aber genau so gehen.
Das ist der genannte Workaround.
Ich will den Ausdruck
std::cout << args<0> << ' ' << args<1> << ' ' << args<2> << ... << '\n';
erzeugen. Kann bei Expression-Templates o.Ä. einen Unterschied machen, hier ist das nur Syntaxzucker.
-
höhenkoller schrieb:
camper schrieb:
StackOps<ArgTypes>::push(L, args) , ...; // Kommaoperator
Das finde ich sehr schön. Allerdings gibt man damit die Garantie auf, dass die Auswertung sequenced ist (der Kommaoperator kann überladen werden).
Sehe ich hier nicht als Problem an, wer diesen Operator überlädt, muss sich über die Konsequenzen klar sein.
höhenkoller schrieb:
Ich wäre deshalb für
StackOps<ArgTypes>::push(L, args)...; // "Semikolon-Operator"
Allerdings ist der Unterschied zu deiner Version wieder recht subtil.
Durchaus brauchbar, hat den kleinen Nachteil, das es nicht an Stellen verwendet kann, an denen bereits jetzt Packexpansionen möglich sind.
camper schrieb:
Evtl. verfeinert man das noch mit ein Kollapsregel, wenn der gleiche Operator, der für die Packexpansion genutzt wird, unmittelbar zuvor oder danach auftritt:
Was ist mit anderen Operatoren in der Nähe?
auto serial_size = 0 + (1 * sizeof(T)) + ... // erlaubt? auto serial_size = 0 + 1 * sizeof(T) + ... // illformed oder wie Zeile oben? auto custom_sizeof = 0.0 + sizeof(T) + ... // "(((0.0 + sizeof(T0)) + sizeof(T1)) + ..." // oder "0.0 + (sizeof(T0) + sizeof(T1) + ...)"? auto custom_sizeof = sizeof...(T)==0 ? 0 : sizeof(T) + ...; // ill-formed? std::cout << args << ... << '\n'; // vermutlich std::cout << (args << ...) << '\n' und std::cout << '\n'
Das kann man größtenteils über die Grammatik handhaben:
multiplicative-pack-expression: pm-expression * ... pm-expression / ... pm-expression % ... pm-expression multiplicative-expression: multiplicative-pack-expression multiplicative-expression * multiplicative-pack-expression multiplicative-expression / multiplicative-pack-expression multiplicative-expression % multiplicative-pack-expression additive-pack-expression: multiplicative-expression + ... multiplicative-expression - ... multiplicative-expression additive-expression: additive-pack-expression: additive-expression + additive-pack-expression additive-expression - additive-pack-expression
analog für die restlichen Operatoren, bei denen das Sinn machen könnte.
auto serial_size = 0 + 1 * sizeof(T) + ... // pattern ist 1*sizeof(T) also 0+1*sizeof(T0)+1*sizeof(T1) usw. auto custom_sizeof = 0.0 + sizeof(T) + ... // pattern ist sizeof(T) also 0.0+sizeof(T0)+sizeof(T1) usw. auto custom_sizeof = sizeof...(T)==0 ? 0 : sizeof(T) + ...; // pattern ist sizeof(T) std::cout << args << ... << '\n'; // pattern ist args
Für Expansionen mit leeren Packs könnte man einen neuen eingebauten Typ (meinetwegen: std::empty_pack_expression_t) einführen. Für die eingebauten Operatoren erklärt man die Semantik einfach so, dass jeweils der andere Operand das Ergebnis darstellt, und bei Überladung kann sich der Programmierer eigene Gedanklen machen.
höhenkoller schrieb:
Wenn hier schon Wunschkonzert ist, das würde ich mir auch noch wünschen:
template <typename T, T... I> index_list { typedef... T indices; // typedef auf Variadische Liste von ints }; template <std::size_t N, typename T=int> using int_seq = make_indices<T, N>::indices; auto wighted_sum = 0 + (int_seq<sizeof...(T)> * sizeof(T))...; // Liste kann hier entpackt werden.
Also Listen als eigenständige Bestandteile der Sprache. Das könnte auch interessant sein. Wir brauchen mehr Lisp in C++
-
Also Listen als eigenständige Bestandteile der Sprache. Das könnte auch interessant sein.
Nun, da klingelt was ... List?, List operation?, List processor?, Lispler?, ach ich komme nicht drauf.
Dann gabs da so einen akademischen Ableger, mit so Macroexpansion und Ellipsen ... ich glaube der hiess Schelm?
-
-
höhenkoller schrieb:
std::cout << "{ " << (args << ' ' <<)... << "}\n"; // Geht sowas ohne Workaround?
Dafür benögen wir noch eine extra Regeln:
Falls das Epansionsmuster( expression )
ist, werden die Klammern bei der Expansion ignoriert.
s << (args << ' ') <<...; // wird zu s << arg0 << ' ' << arg1 << ' ' usw. s << ((args << ' ')) <<...; // wird zu s << (arg0 << ' ') << (arg1 << ' ') usw.
Außerdem fehlt mir noch ein lazy-Operator um vorzeitige Packexpansion zu verhindern. Ich nehme mal -> als Präfixoperator:
template <int... i> index_list { using register int pack = i; // etwas sinnvollere Syntax für höhencollers Idee }; template <int rows, int columns> struct matrix { double m[rows][colums]; ... }; template <int X, int Y, int Z> matrix<X, Z> operator*(matrix<X,Y> a, matrix<Y,Z>) { using register int x_list = make_indexes<X>::pack; using register int y_list = make_indexes<y>::pack; using register int z_list = make_indexes<z>::pack; return matrix<X, Z>{ a.m[->->x_list][y_list]*b.m[y_list][->z_list]+... ... ... }; }