Wozu decltype, wenn man auto hat?
-
Wozu decltype, wenn man auto hat?
-
c+++++++++ schrieb:
Wozu decltype, wenn man auto hat?
Für nutzliche typedefs.
Kannst ja nur lokale Variablen autoen. Attribute nicht.
-
Das Schlüsselwort
decltypeist viel mächtiger alsauto, treffender wäre also die Frage "wozuauto, wenn mandecltypehat?"Letzteres besteht ja primär aus Syntaxgründen, prinzipiell könnte man folgendes Makro definieren:
#define AUTO(var, expr) decltype(expr) var = expr;Und dann wird
autonoch für die neue Funktionsdeklaration eingesetzt. Sonst noch irgendwo?
-
Um es noch einmal zu sagen:
int main() { long double test(.0); auto n(test);//Wirklich nichts anderes als decltype(test) g(test); }auto ist aber so gesehen etwas besser, da:
int main() { std::vector<int> a; decltype(a.begin()) iter(a.begin()); auto iter_(a.begin()); }@Nexus: Kannst du mir einen Link für das mit der Funktionsdeklaration geben? :xmas1:
-
Nexus schrieb:
Letzteres besteht ja primär aus Syntaxgründen, prinzipiell könnte man folgendes Makro definieren:
#define AUTO(var, expr) decltype(expr) var = expr;Damit funktioniert aber folgendes nicht
AUTO fun = [](int x, int y){return x+y;}und auch das nicht
AUTO dings = {2,3,5,7,11,13,17,19,23,29};und dann ist auto ja auch nur ein Typ-Platzhalter, der entsprechend dekoriert werden kann:
vector<auto> blah = myfunction();oder
auto const& x = binky();Kurz um: Mit auto kann man decltype nicht emulieren. Mit decltype kann man auto nicht emulieren. Damit wär die Frage beantwortet.
-
krümelkacker schrieb:
vector<auto> blah = myfunction();
Oh, den kannte ich noch nicht.
-
constexpr int binky(){return 5;} int main() { decltype(binky()) const& x = binky(); }:xmas1: :xmas2:
volkard schrieb:
krümelkacker schrieb:
vector<auto> blah = myfunction();
Oh, den kannte ich noch nicht.
-
krümelkacker schrieb:
Damit funktioniert aber folgendes nicht
AUTO fun = [](int x, int y){return x+y;}und auch das nicht
AUTO dings = {2,3,5,7,11,13,17,19,23,29};Man müsste
AUTO(var, expr)stattAUTO var = exprschreiben... Und eventuell Klammern oder Variadic Macros wegen der Kommas verwenden. Oder meinst du, auch das geht nicht? Falls ja, weshalb? Kann mandecltypenicht auf Lambdas oder Initialisierungslisten anwenden?krümelkacker schrieb:
und dann ist auto ja auch nur ein Typ-Platzhalter, der entsprechend dekoriert werden kann:
vector<auto> blah = myfunction();Ah, das kannte ich ebenfalls nicht. Interessant!

-
Nexus schrieb:
krümelkacker schrieb:
und dann ist auto ja auch nur ein Typ-Platzhalter, der entsprechend dekoriert werden kann:
vector<auto> blah = myfunction();Ah, das kannte ich ebenfalls nicht. Interessant!

Naja, gcc kennt das auch nicht.
Ist es wirklich erlaubt?Außerdem habe ich Angst vor
auto<auto> blah = myfunction();:xmas2:
-
@Nexus:
Ein Lambdaausdruck darf nicht innerhalb sizeof und decltype auftauchen. Und da {2,3,5,7} kein richtiger Ausdruck ist, klappt hier decltype auch nicht. Aber für auto gibt es eine Sonderregel:auto list = {2,3,5,7,11}; // decltype(list) -> std::initializer_list<int>Damit funktioniert dann auch das hier:
for (int x : {2,3,5,7,11}) { cout << x << endl; }weil hier hinter der Kulisse so auto verwendet wird:
{ auto && __range = {2,3,5,7,11}; for (auto it = begin(__range), ee = end(__range); it != ee; ++it) { int x = *it; cout << x << endl; } }
-
Danke für die Erklärung.
Warum die Regel, dass man kein
decltypeauf Lambdas und Initializer-Lists anwenden kann? Indirekt kommt man durch Variablen sowieso an den Typen.Und wieso ist
{2,3,5,7}kein richtiger Ausdruck? Weil es unter Anderem als Argumentliste für den Konstruktor oder als Aggregat-Initialisierungsliste verwendet werden kann?
-
{ auto && __range = {2,3,5,7,11}; int x; for (auto it = begin(__range);it != end(__range); ++it) { x = *it; cout << *it << '\n'; } }1. Ist es so schlimm, wenn end() immer wieder aufgerufen werden muss?
2. Wird x nicht globaler deklariert?Edit: ach ja, und- was genau ist end()? Kann dazu nichts finden... findet es das Ende eines C-Arrays, oder wa?
-
Hacker schrieb:
Edit: ach ja, und- was genau ist end()? Kann dazu nichts finden... findet es das Ende eines C-Arrays, oder wa?
Ja. Und bei normalen Containern ruft es cont.end() auf. Ganz automatisch. Wohl mit SFINAE implelentiert.
-
Edit: ach ja, und- was genau ist end()? Kann dazu nichts finden... findet es das Ende eines C-Arrays, oder wa?
Schau mal hier: http://en.cppreference.com/w/cpp/iterator/end
Das sollte dir weiterhelfen

-
Nexus schrieb:
Danke für die Erklärung.
Warum die Regel, dass man kein
decltypeauf Lambdas und Initializer-Lists anwenden kann? Indirekt kommt man durch Variablen sowieso an den Typen.Und wieso ist
{2,3,5,7}kein richtiger Ausdruck? Weil es unter Anderem als Argumentliste für den Konstruktor oder als Aggregat-Initialisierungsliste verwendet werden kann?Unter anderem das. Außerdem kann man initialiser_list<A> nicht in initialiser_list<B> konvertieren. Andererseits ist
struct foo { foo(std::initialiser_list<int>); }; foo x = { 'a', 'b' }; // okkein Problem, aus dem Kontext der Initialisierung heraus ergibt sich, dass ein std::initialiser_list<int> benötigt wird.
Hacker schrieb:
{ auto && __range = {2,3,5,7,11}; int x; for (auto it = begin(__range);it != end(__range); ++it) { x = *it; cout << *it << '\n'; } }1. Ist es so schlimm, wenn end() immer wieder aufgerufen werden muss?
2. Wird x nicht globaler deklariert?Edit: ach ja, und- was genau ist end()? Kann dazu nichts finden... findet es das Ende eines C-Arrays, oder wa?
krümelkackers Code sieht wie aus dem Standard kopiert aus. Da __range im allgemeinen Fall des range-based for-loops auch ein normaler Container sein kann, gibt es semantische Unterschiede zwischen beiden Varianten. Da die Spezifikation ist wie sie ist, wird damit ausgedrückt, dass der end-Iterator in einem solchen loop gültig bleiben muss.
-
volkard schrieb:
Wohl mit SFINAE implelentiert.
http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error, ganz versteh ich das noch nicht. Also, man kann beliebige Template-Parameter übergeben, und es kommt zu keinem Fehler?
struct Test { typedef int foo; }; template <typename T> void f(typename T::foo) {} // Definition #1 template <typename T> void f(T) {} // Definition #2 int main() { f<Test>(10); // Call #1. f<int>(10); // Call #2. Without error (even though there is no int::foo) thanks to SFINAE. }Das verwirrt mich aufs extremste...T::foo gibt es ja sowieso nur bei Test, oder etwa nicht? also müsste bei Call#1 Def#1 und bei Call#2 entsprechen Def#2 aufgerufen werden? Logischerweise?
:xmas1:
-
Nexus schrieb:
Warum die Regel, dass man kein
decltypeauf Lambdas und Initializer-Lists anwenden kann?Jedes Lambda hat seinen eigenen Typ, egal, ob es völlig mit einem anderen Lambda übereinstimmt oder nicht. Sonst müsste der Compiler in der Funtkion alle Lambdas auf Äquivalenz untersuchen und ggf. zusammenfügen. Und weil in dem AUTO()-Makro eben zwei Lambdas vorkommen, die zwar äquivalent sind, aber vom Compiler nicht als solche erkannt werden (müssen), gibt es keinen Ausdruck, den du rechts hinschreiben könntest, um den Typ des decltypes links zu bekommen.
(Alle Angaben ohne Gewähr)
-
@Hacker: Richtig. SFINAE hat viel mit Template-Spezialisierung und Fallunterscheidung zu tun. Dein Programm, das du richtig analysiert hast, ist noch die einfache Variante. Kompliziertere Varianten benutzen Funktionen/Klassen, die für verschiedene Typen überladen/spezialisiert sind und verschiedene Rückgabetypen mit verschiedenen Größen haben, werten dann die Größe des Rückgabetyps für die Überladung, die der Compiler für den gegebenen Typ auswählt, aus und benutzen je nach Größe eine unterschiedliche Klassenspezialisierung.
Schau dir auch mal Type Traits wie z.B. std::is_base_of, std::is_same, std::is_integral o.ä. an.
-
Hacker schrieb:
Das verwirrt mich aufs extremste...T::foo gibt es ja sowieso nur bei Test, oder etwa nicht? also müsste bei Call#1 Def#1 und bei Call#2 entsprechen Def#2 aufgerufen werden? Logischerweise?
Ganz logisch ist das nicht. Wenn der Standard SFINAE nicht definieren wuerde, wuerde der compiler beim 2. fall versuchen int::foo zu erzeugen und einen Fehler schmeissen. Durch SFNIAE wird dem compiler aber mitgeteilt: wenn es int::foo nicht gibt, ignoriere das erstmal und such nach einer anderen Version der Funktion, die funktioniert. Das ist der Kern der Regel.
In diesem Fall wird SFINAE angesprochen, weil man damit gezielt funktionen ausschalten kann, wenn der Templateparameter ebstimmte Eigenschaften hat.
Zum Beispiel kannst du sowas machen:
template<bool T> struct TriggerSFINAE{}; template<> struct TriggerSFINAE<true>{typedef int* type;}; template<class T> struct IsArithmetic{ static const bool value = false; }; template<> struct IsArithmetic<double>{ static const bool value = true; }; ... //und dann sowas wie: //die funktion soll nur auf arithmetischen typen funktionieren: template<class T> void foo(T t,typename TriggerSFINAE<IsArithmetic<T>::value>::type t = nullptr){ std::cout<<"ist arithmetisch"; } //hier ne implementation die sonst aufgerufen wird: void foo(T t,typename TriggerSFINAE<!IsArithmetic<T>::value>::type t = nullptr){ std::cout<<"ist nicht arithmetisch"; }Das ist besonders sinnvoll, wenn der Funktionsaufruf sonst mehrdeutig waer, also von der Parameterliste beide Versionen passen wuerden.
-
otze schrieb:
Hacker schrieb:
Das verwirrt mich aufs extremste...T::foo gibt es ja sowieso nur bei Test, oder etwa nicht? also müsste bei Call#1 Def#1 und bei Call#2 entsprechen Def#2 aufgerufen werden? Logischerweise?
Ganz logisch ist das nicht. Wenn der Standard SFINAE nicht definieren wuerde, wuerde der compiler beim 2. fall versuchen int::foo zu erzeugen und einen Fehler schmeissen. Durch SFNIAE wird dem compiler aber mitgeteilt: wenn es int::foo nicht gibt, ignoriere das erstmal und such nach einer anderen Version der Funktion, die funktioniert. Das ist der Kern der Regel.
Ah, jetzt verstehe ich etwas. Das Problem ist, ich dachte dass eben das selbstverständlich ist, weshalb ich zuerst dachte, SFINAE sei irgendeine Erweiterung

:xmas1: