Variadic Templates
-
Ich habe mit dem genannten Sprachmittel noch Verständnisschwierigkeiten:
template<typename T> struct Foo{ }; template<typename T, typename U> void Bar() { } template<typename... T> //1. Werden cv-Qualifier auch mitgespeichert? void f(Foo<T> const&... x) { } //2. Ist das gültig? template<typename... T> void g(T... x, T... y) { } //3. Ist das gültig? template<typename... T> struct S{ }; template<template<class...> class T, typename... U> using Alias = T<U>; //4. Ist das gültig? template<template<class...> class T, typename... U> using Alias = T<U...>; //5. Ist das gültig? template<typename... T> char a(T... x) { } typename<typename... T> char b(T... x) { return a<T>(x); //6. Ist das gültig? } int main() { /* Man denke sich die Meta-Typ-Funktion metatype(x): metatype(int) = Nativer Typ metatype(5) = RValue metatype(Foo) = <typename>-Klassentemplate metatype(Foo<int>) = Klassentyp metatype(Bar) = <typename, typename>-Funktionstemplate metatype(Bar<int, int>) = void(void)-Funktion metatype(&Bar<int, int>) = void(void)-Funktionszeiger */ S<int, float>(); //7. Was ist hier metatype(T)? //8. Was ist hier metatype(T...)? }Der Kern meines Verständnisproblems liegt beim Verstehen vom Typ von Parameter-Packs. Kann mir jemand aushelfen?
-
1. Ja, klar.
2. Ja. Das erstellt dann n Parameter, vom Typ Foo mit dem Templateargument aus dem Pack am entsprechenden Index.
3. Nein, der Compiler kann die Argumente nicht zuordnen.
4. Nein, ein Parameter-Pack muss ausgerollt werden.
5. Das ist gueltig.
6. Nein, selber Grund wie 4.
7/8 kp, versteh ich auch nicht.
-
Wenn ich dein "metatype" Konstrukt richtig verstehe:
7: ungültig aus dem selben Grund wie 4
8: Nativer Typ, Nativer TypL1nd3nm4y3r schrieb:
Der Kern meines Verständnisproblems liegt beim Verstehen vom Typ von Parameter-Packs. Kann mir jemand aushelfen?
Ein Parameter-Pack hat nicht *einen* Typ, sondern stellt eine Liste an Typen dar.
-
Danke schomal für die Antworten.
Zu 3.:
Verstehe ich nicht wieso er das nicht können sollte.g(5, 'c', 7, 'd')ist eigentlich eindeutig, wenn es mal nicht eindeutig sein soll könnte man auch einfach einen Compilerfehler wegen Mehrdeutigkeit ausgeben. Oder alternativ gibt man das Parameterpack manuell an und lässt den Compiler konvertieren. Wie auch immer...Zu 6.:
template<typename... T> char a(T... x) { } typename<typename... T> char b(T... x) { return a<T...>(x); //9. So wäre es logischerweise korrekt, ja? }hustbaer schrieb:
L1nd3nm4y3r schrieb:
Der Kern meines Verständnisproblems liegt beim Verstehen vom Typ von Parameter-Packs. Kann mir jemand aushelfen?
Ein Parameter-Pack hat nicht *einen* Typ, sondern stellt eine Liste an Typen dar.
Das war meinerseits etwas unglücklich ausgedrückt. Ich meinte den Pseudo-Meta-Typ, so wie LValues, Typen oder Funktionstemplates. Ich hab kein Gespür dafür wie man damit umzugehen hat, bei z.B. Templates weiß ich, dass und wie ich sie instanziieren kann, bei Parameterpacks ist es nicht da.
Kann man pauschal sagen, dass so ein Parameterpack / Typenverbund
template<typename... T> struct A { /*Das T in diesem Scope ist gemeint*/ };nutzlos und sinnlos ist wenn man weder den
sizeof...()-Operator, noch den "Auspackoperator"...auf es anwendet? Oder kann man es doch irgendwie irgendwo einsetzen?Noch ein paar Fälle die mir irgendwie unklar sind (bzw. zu welchen ich höchstens eine Vermutung habe):
template<typename... T> void f(T&... x) { //10. Was ist decltype(x)? Nicht definiert nach Standard? //11. Was ist metatype(x)? Ein Quasi-Variablen-Pack? //12. Was ist decltype(x...)? //13. Was ist metatype(x...)? Bei z.B. f<int, char> einfach (LValue-Ref, LValue-Ref)? } template<typename... T> void g(T&... x) { f<T...> //14. Ist das gültig? (&(++(x...))); //15. Ist das gültig? (In anderen Worten, darf man so viele Operatoren dranschreiben wie man möchte?) } template<typename... T> void h(T const&... x) { f(std::sin(x...)); //16. Ist das gültig? Oder muss es "f(std::sin(x)...);" lauten? } template<typename T> char f(T& x) { return '\0'; } //17. Ist das eine gültige Überladung? int main() { f(5); //18. Gehe ich richtig davon aus dass hier die char-Variante aufgerufen wird? f<int>(5); //19. Dasselbe wie 18. } template<typename... T> struct C { T... x; //20. Ist das gültig? };
-
Falls du an einer tiefergehenden Erklärung von Variadic Templates interessiert bist, könntest du dir Variadic Templates Are Funadic anschauen. Andrei Alexandrescu ist wahrscheinlich der Template-Metaprogrammierungs-Experte überhaupt. Er geht in der Präsentation auf die genauen Regeln des Pack-Expansion-Operators ein (ziemlich am Anfang).
-
L1nd3nm4y3r schrieb:
Danke schomal für die Antworten.
Zu 3.:
Verstehe ich nicht wieso er das nicht können sollte.g(5, 'c', 7, 'd')ist eigentlich eindeutig, wenn es mal nicht eindeutig sein soll könnte man auch einfach einen Compilerfehler wegen Mehrdeutigkeit ausgeben. Oder alternativ gibt man das Parameterpack manuell an und lässt den Compiler konvertieren. Wie auch immer...Das man in Einzelfällen ad-hoc-Lösungen anbieten könnte, bedeutet nicht, dass ohne weiteres eine allgemeine nützliche Regel abgeleitet werden kann. Würde man solche ad-hoc-Lösungen zulassen, hat man nur Probleme, wenn später doch mal eine bessere Regelung gefunden wird, die ggf. davon abweicht. Die gegenwärtigen Regeln vermeiden die Problematik: in primären Klassentemplates kann nur der letzte Templateparameter ein Pack sein, in Funktionstemplates darf nur der letzte Funktionsparameter ein Pack sein (mehrere Templateparameterpacks sind für Funktionstemplates oder partielle Klassentemplatespezialisierungen durchaus möglich).
L1nd3nm4y3r schrieb:
hustbaer schrieb:
L1nd3nm4y3r schrieb:
Der Kern meines Verständnisproblems liegt beim Verstehen vom Typ von Parameter-Packs. Kann mir jemand aushelfen?
Ein Parameter-Pack hat nicht *einen* Typ, sondern stellt eine Liste an Typen dar.
Das war meinerseits etwas unglücklich ausgedrückt. Ich meinte den Pseudo-Meta-Typ, so wie LValues, Typen oder Funktionstemplates. Ich hab kein Gespür dafür wie man damit umzugehen hat, bei z.B. Templates weiß ich, dass und wie ich sie instanziieren kann, bei Parameterpacks ist es nicht da.
Packs passen nicht in diese Kategorien, am ehesten könnte man Packs als Platzhalter ansehen. Abgesehen vom sizeof...-Operator können Packs nicht ausserhalb von Packexpansionen verwendet werden.
Packexpansionen nur in bestimmten Kontexten auftauchen:
- in der Templateargumentliste einer Templatespezialisierung,
- in der Basisklassenliste einer Klasse,
- in Capturelisten,
- als Funktionsargumente eines Funktionsaufrufes,
- in Initialisierungslisten und Konstruktorinitialisierungslisten,
- in Attributlisten, Alignmentspezifizieren und in Exceptionspezifikationen
Das bedeutet unter anderem, dass Packs niemals das Template verlassen, zu dem sie gehören.template<typename... T> struct C { T... x; //20. Ist das gültig? };Unterstellen wir mal, das wäre zulässig (ist es nicht). Was sollte es bedeuten, wenn ich irgendwo in meinem Code
C<int,bool,double>::xschreibe?
Damit soll nicht gesagt sein, dass man die Sprache nicht in dieser Hinsicht erweitern könnte oder dass das sinnlos wöre.
Der Anwendungsfall für C dürfte aber bereits durch std::tuple weitgehend abgedeckt sein.template<typename... T> void g(T&... x) { f<T...> //14. Ist das gültig? (&(++(x...))); //15. Ist das gültig? (In anderen Worten, darf man so viele Operatoren dranschreiben wie man möchte?) }Das Problem beginnt hier damit, dass die Packexpansion keine Argumentliste des Funkitonsaufrufes ist, also ein unzulässiger Kontext.
Wenn ich mal unterstelle, dass duf<T...>(&(++(x))...)schreiben wolltest, dann ja, das ist möglich. Die Operatoren sind hier Teile des Expansionsmusters und werden auf jedes einzelne Element des Packs angewendet.
template<typename... T> void h(T const&... x) { f(std::sin(x...)); //16. Ist das gültig? Oder muss es "f(std::sin(x)...);" lauten? }Beides möglich mit unterschiedlicher Bedeutung. Die erste Variante expandiert x für den Aufruf von sin, und weil sin nur ein einzelnes Argument nimmt, bekommst du Probleme, falls dein Pack mal mehr als ein Element hat oder dieses vom falschen Typ ist.
-
camper schrieb:
falls dein Pack mal mehr als ein Element hat
Du meinst wohl nicht genau ein Element.
Ein Parameter-Pack hat nicht *einen* Typ, sondern stellt eine Liste an Typen dar.
Ein Parameter-Pack kann auch eine Liste von Werten darstellen.
Oder alternativ gibt man das Parameterpack manuell an und lässt den Compiler konvertieren.
Ja, aber hier tritt genau dasselbe Problem auf. Der Compiler kann die Template-Argumente nicht den Template-Parametern zuordnen, weil beide eine beliebige Anzahl von Argumenten nehmen koennten.
Ich meinte den Pseudo-Meta-Typ, so wie LValues, Typen oder Funktionstemplates
Das ist ganz einfach. Ein template-parameter-pack ist ein Template-Parameter. Ein function-parameter-pack ist ein Funktionsparameter. Nur dass diese statt genau einem Argument beliebig viele nehmen koennen. Und ein solcher Parameter kann dann wieder in eine Liste expandiert werden. Wenn du dich ein wenig damit beschaeftigst und herumexperimentierst, kommt das Verstaendnis von selbst.