TMP Metafunktion - Name Lookup verhaelt sich komisch
-
Halloele,
Folgender Code sei erstmal gegeben:
template <typename T> struct Identity { typedef T result; }; template <typename T, T v> struct Constant { typedef T type; static constexpr T value = v; }; template <int N> using Int = Constant<int, N>;Weiters habe man eine Metafunktion 'plus', die zwei Werte addiert...
template <typename left, typename right> struct plus : Identity<Constant<decltype(left::value + right::value), left::value + right::value>> {};... sowie eine Metafunktion bind2nd, die den Zweiten Parameter einer Metafunktion bindet und ein entsprechendes Template zurueckgibt...
template <template <typename, typename, typename...> class function, typename arg2> struct bind2nd { template <typename arg1, typename... args> struct result : function<arg1, arg2, args...> // ^^^^^^ {}; };Warum ich diese Stelle hervorgehoben habe, dazu komme ich gleich.
Nun schreibe ich eine Metafunktion 'plus1', die zum Argument immer 1 hinzuaddiert, indem sie das zweite Argument mittels bind2nd bindet:
template <typename arg> struct plus1 : bind2nd<plus, Int<1>>::template result<arg> // ^^^^^^ {};Gibt man nun das Ergebnis der Metafunktion aus...
std::cout << std::boolalpha << std::is_same<plus1<Int<100>>::result, Int<101>>::value;... so ist das Ergebnis false. Was hat es nun mit den hervorgehobenen Stellen auf sich? Aendere ich den namen des nested Templates auf "binder", so stimmt das Ergebnis! Was zur hoelle treibt mein Compiler hier? Wo nimmt er result her, wenn nicht aus bind2nd?
Edit: Hier noch 2 Ideone-Links zum Vergleich:
"result": https://ideone.com/chRnBB
"binder": https://ideone.com/KIVHCR
Wie man sieht, ist der Code 1:1 identisch, bis auf den Namen des binder-Templates.Gruesse,
Der Kellerautomat
-
Im zweiten Fall bezieht sich das "result" in der Ausgabe auf das "result" in Identity, im ersten auf das in bind2nd -- die Basisklasse der plus1-Konkretisierung.
-
Das kann nicht sein, da das result in bind2nd ein Template ist, kein Typ. Der Code duerfte dann nicht kompilieren.
-
std::cout << std::boolalpha << std::is_same<plus1<Int<100>>::Identity::result, Int<101>>::value << '\n'; std::cout << std::boolalpha << std::is_same<plus1<Int<100>>::result, Int<101>>::value << '\n';Dass bei dieser Ausgabe result in plus1 das result aus Identity verdeckt ist doch eigentlich klar.
-
1. Fall
plus1<Int<100>> --> kein result | | Basisklasse \|/ bind2nd<plus, Int<1>>::result<Int<100>> --> injected class name (14.6.1/1) fertig2. Fall
plus1<Int<100>> --> kein result | | Basisklasse \|/ bind2nd<plus, Int<1>>::binder<Int<100>> --> kein result | | Basisklasse \|/ plus<Int<100>, Int<1>> --> kein result | | Basisklasse \|/ Identity<Constant<int, 101>> --> typedef name result
-
Nur damit ich das richtig verstehe: Im ersten Fall ist ::result also bind2nd<plus, Int<1>>::result<Int<100>>, und wenn ich ::result::result schreibe, bezieht sich das wieder auf die selbe Instanzierung?
-
Kellerautomat schrieb:
Nur damit ich das richtig verstehe: Im ersten Fall ist ::result also bind2nd<plus, Int<1>>::result<Int<100>>, und wenn ich ::result::result schreibe, bezieht sich das wieder auf die selbe Instanzierung?
ja
struct A { static const int value = 2; }; const int A::value; template <typename T> struct B { static const int value = sizeof(T); }; template <typename T> const int B<T>::value; #include <iostream> int main() { std::cout << A::A::A::A::A::A::A::value << '\n'; std::cout << ( B<char>::B::template B<int>::B::value ) << '\n'; }g++ kriegt es mal wieder nicht hin, mit B als Templatenamen (die Klammern und das Schlüsselwort template nur, damit g++ wenigstens eine vernünftige Fehlermeldung auswirft).
Der Code ist korrekt.
-
Das scheint mir aber ziemlich kaputt. Warum sollte ich von ausserhalb der Klasse T::T schreiben wollen? Hat das irgendeinen Nutzen?
-
Kellerautomat schrieb:
Das scheint mir aber ziemlich kaputt. Warum sollte ich von ausserhalb der Klasse T::T schreiben wollen? Hat das irgendeinen Nutzen?
Ich würde es für seltsam halten, wenn ein Klassenname innerhalb der Klasse plötzlich etwas anders als die Klasse bezeichnen würde. Und ganz sicher willst du nicht vorschlagen, das qualifiziertes Lookup andere Namen finden sollte als unqualifiziertes Lookup im gleichen Scope.
struct A { typedef int X; }; struct X : A { X foo(); // struct X und nicht A::X };
-
Das Problem ist hier noch ein anderes: Identity::result ist in plus (und allem, was in der Hierarchie danach kommt) gar nicht sichtbar (will sagen: plus::result ist kein gültiger Bezeichner), weil der Compiler zu der Zeit, zu der die Template geparst wird, noch nicht wissen kann, ob Identity::result ein Typ ist (oder überhaupt existiert). Zwischen diesem Punkt und der Konkretisierung der Template könnte jemand eine Spezialisierung einziehen, in der das nicht der Fall ist. Schlimmer noch umgekehrt: Eine Spezialisierung könnte etwas deklarieren, was der Implementation einer Methode der Vorlage eine völlig andere Bedeutung verleiht.
In Vorlagen müssen dependent names von einer Basisklassentemplate ausdrücklich geerbt werden; dies geschieht nicht automatisch. Etwa:
template<typename T> struct identity { typedef T type; }; template<typename T> struct foo : identity<T> { type x; // geht nicht; type ist dependent! }; template<typename T> struct foo : identity<T> { using typename identity<T>::type; type x; // jetzt ist type bekannt. };Wenn ich das mal auf deinen Code anwende, also
template <template <typename, typename, typename...> class function, typename arg2> struct bind2nd { template <typename arg1, typename... args> struct result : function<arg1, arg2, args...> { using typename function<arg1, arg2, args...>::result; }; };schreibe (in plus entsprechend), erhalte ich folgende Fehlermeldung:
test.cc:27:57: Fehler: »function<arg1, arg2, args ...>::result« benennt Konstruktor in »bind2nd<function, arg2>::result<arg1, args>«...was ja irgendwo auch Sinn macht. Einer Klasse einen gleichnamigen Bezeichner unterordnen zu wollen, funktioniert aus einer ganzen Reihe von Gründen stumpf nicht.
-
Danke fuer eure Erklaerungen, das ergibt fuer mich jetzt alles Sinn.
