Und mal wieder variadische Templates...
-
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]+... ... ... }; }
-
Da werden die Javaner richtig doof aus der Wäsche gucken.
Zum Folgenden "Problem" bzw. der folgenden Unklarheit schlage ich vor, dass Ausdrücke mit einer Ellipsis als Operand stärker gebunden werden:
camper schrieb:
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?
Klar, wird (sobald die 1 weg ist) zu
(sizeof(T) + ...)
, was wiederum ganz klar definiert istauto serial_size = 0 + 1 * sizeof(T) + ... // illformed oder wie Zeile oben?
Hier greift die Regel ganz normal: Es ist
1 * (sizeof(T) + ...)
...auto custom_sizeof = sizeof...(T)==0 ? 0 : sizeof(T) + ...; // ill-formed?
Nö! Dank der stärkeren Bindung wird das wieder ok sein.
std::cout << args << ... << '\n'; // vermutlich std::cout << (args << ...) << '\n' und std::cout << '\n'
Ja, das wird dann nicht möglich sein.
-
camper schrieb:
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.
Oh, das ist schön!
-
camper schrieb:
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.
Das Problem ist das, dass damit Klasseninvarianten gebrochen werden können. Du wirst das wohl auf Machiavelli verweisend akzeptieren, aber ich möchte meine Klassennutzung nicht willkürlich einschränken. Lieber nehme ich die alte Syntax mit dem Array. Vieleicht sollte für den Kommaoperator mit Pack die Auswertung der Argumente gesequenced sein.
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.
Das löst auch das ?:-Problem (@Sone: Hast du schon den Fall sizeof...(T)==0 angeschaut?). Der Unterschied ist allerdings detektierbar:
decltype(variable + args + ...); // decltype(variable) hat spezielle Bedeutung
template <int... i> index_list { using register int pack = i; // etwas sinnvollere Syntax für höhencollers Idee };
Ich will die gleiche Syntax auch für Typlisten um perfect forwarding für Template-Parameter zu ermöglichen. Momentan habe ich für mich die Richtlinie, alle Werte als integral_constant zu übergeben.
template <tempalte<register...>class T, register F, typename... Args> T<F...> template_parameter_forward(Args&&... args) { return T<typename F...>(std::forward<Args>(args)...); } // Um Variablen zu deklarieren (da müsste deine Definition ggf. erweitert werden): typename F var...; auto a = template_parameter_forward<vector, int>(); auto b = template_parameter_forward<array, 5>(); auto c = template_parameter_forward<matrix, make_indices<2>::pack>(); auto d = template_parameter_forward<matrix, register<1, 2>>(); auto e = template_parameter_forward<vector, register<int, std::allocator>>();
Das kann man dann mit dem Int-Pack kombinieren:
template <int... i> index_list { using register pack = i; // allgemeinere Syntax };
Gefällt mir echt
typedef std::tuple<...> tuple; tuple t; return 0 + std::get<int_seq_pack<std::tuple_size<tuple>::value>(t) + ...;
-
Syntax könnte auch so aussehen (wir wissen ja schließlich bereits, wie Packs zu deklarieren sind, kein Grund, etwas neues zu erfinden, die Syntax von typedef zu erweitern funktioniert aber wahrscheinlich nicht).
int... int_pack = 1, 2, 3; int... int_pack2 = int_pack...; using... Args = int, double, char; Args... args = 0, 0.0, '\0'; template <typename... T> using... pointer_added = T*...; using... template <typename> class funcs = std::plus, std::minus; template <typename... T> void foo(T&&... x) { using... Args = T&&...; Args... args = x...; }
Der Schreibvorteil deines template_parameter_forward ist mir nicht so klar.
-
camper schrieb:
Syntax könnte auch so aussehen (wir wissen ja schließlich bereits, wie Packs zu deklarieren sind, kein Grund, etwas neues zu erfinden, die Syntax von typedef zu erweitern funktioniert aber wahrscheinlich nicht).
int... int_pack = 1, 2, 3; int... int_pack2 = int_pack...; using Args... = int, double, char; Args... args = 0, 0.0, '\0'; template <typename... T> using pointer_added... = T*...; using template <typename> class funcs... = std::plus, std::minus; template <typename... T> void foo(T&&... x) { using Args... = T&&...; Args... args = x...; }
Mir gefällt wie sich der Thread entwickelt. Wer schreibt das Proposal?
-
höhenkoller schrieb:
camper schrieb:
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.
Das Problem ist das, dass damit Klasseninvarianten gebrochen werden können. Du wirst das wohl auf Machiavelli verweisend akzeptieren, aber ich möchte meine Klassennutzung nicht willkürlich einschränken. Lieber nehme ich die alte Syntax mit dem Array. Vieleicht sollte für den Kommaoperator mit Pack die Auswertung der Argumente gesequenced sein.
Ich mag Ausnahmeregelungen nicht.
Anfreunden könnte ich mich mitexpression ...
wird expandiert als Ausdruck, der die einzelnen Elemente mit dem eingebauten Kommaoperator verknüpft mit der Maßgabe, dass wenn die Grammatik an der Stelle auch
expression-list
zulässt (Funktionsaufrufe, Initialisierung), diese Interpretation vorrang hat. Wer das Andere will, kann ja einfach eine Klammer verwenden
(expression ...)