Lustiger VC6-Bug oder bin ich zu blöd?
-
Hallo Freunde der C++ Programmierung,
ich sitze hier gerade mal wieder und töte den ein oder anderen Käfer in meinem Loki-Port.
Wie jedesmal, wenn ich mich mit dem Code beschäftige, habe ich auch heute wieder einen (für mich) neuen VC6-Bug entdeckt, den ich euch nicht vorenthalten wollte.Bekanntlich ist dem VC 6 "explicit template argument specification for member-functions" nicht bekannt. Um das Fehlen dieses Features zu umgehen setzt man normalerweise Dummy-Parameter ein, damit der Compiler alle Templateparameter implizit herleiten kann.
Nun gibt es in der Loki-Library folgende Methode (Beispiel natürlich extrem vereinfacht):
template </*...*/typename ResultType/*...*/> class FnDispatcher { public: //... template <class SomeLhs, class SomeRhs, ResultType (*callback)(SomeLhs&, SomeRhs&)> void Add(){/*...*/} };
Man muss hier also die type-Paramter SomeLhs und SomeRhs, sowie den non-type-Parameter callback explizit angeben:
void Func(Poly& p, Poly& q) {} ... FnDispatcher<Shape> dis; dis.Add<Poly, Poly, &AFunc>();
Der VC 6 Workaround scheint trivial und geht leicht von der Hand:
template<class S1, class S2, class R, R (*)(S1&,S2&)> struct Helper {}; template </*...*/typename ResultType/*...*/> class FnDispatcher { public: //... template <class SomeLhs, class SomeRhs, ResultType (*callback)(SomeLhs&, SomeRhs&)> void Add(Helper<SomeLhs, SomeRhs, ResultType, callback> ) {} }; // ... FnDispatcher<void> f; f.Add(Helper<Rectangle, Rectangle, void, &Func>());
Und nun kommt das lustige an der Sache. Der Code wird einwandfrei kompiliert und gelinkt. Zur Laufzeit passiert aber nichts. Gar nichts. Add wird *niemals* aufgerufen.
Und schaut man sich den generierten Assemblercode an, stellt man fest, dass dieser lustige Microsoft-Compiler überhaupt keinen Code für einen Aufruf generiert hat.
Also: Bug des VCs oder Bug in meinem Gehirn?
PS: Natürlich habe ich mir mittlerweile auch einen funktionierenden Workaround ausgedacht. Gefallen tut mir dieser aber nicht. Falls also einer von euch eine geniale Idee hat, immer her damit
-
eii hume geh schlafen, ich glaub du hast nachholbedarf, *gg*.
kann das sein ?
-
Sorry für meine blöde Frage bin noch Anfänger aber was ist ein Loki Port ???
mfg
Crash
-
Ein Grund in der Fachwelt (zu Recht) als Guru anerkannt zu werden.
-
Wie compilierst du denn? Ich gehe mal davon aus, dass du in deiner Add Funktion noch ein bisschen was machst, oder als Debug Version compilierst. Bei der Release Einstellung würde VC den Aufruf komplett verwerfen, da dort sowiso nix passiert.
-
Hallo,
keine Angst. In der Add-Funktion passiert schon was. Das was da passiert erinnert in seiner Übersichtlichkeit aber etwas an Mettwurst. Deshalb und weil für den eigentlichen Punkt nicht relevant, habe euch den Code erspart.
-
was mich interessiert - hast du in Add die richtige adresse von func?
bzw. probier mal ein void f(void); funktion zu übergeben und lass die template parameter für result type und so mal weg - ob er das schluckt.
wenn nicht, dann scheint er probleme mit funktionszeigern als template parameter zu haben...
-
hast du in Add die richtige adresse von func?
Ich komme in Add überhaupt nicht rein.
bzw. probier mal ein void f(void); funktion zu übergeben und lass die template parameter für result type und so mal weg - ob er das schluckt.
Selbst das Minimalbeispiel verhält sich so:
void Func() { cout << "Func" << endl; } template <void (*callback)()> struct Helper {}; class Foo { public: template < void (*callback)() > void Add(Helper<callback> ) { callback(); } }; int main() { Foo f; f.Add(Helper<&Func>()); }
wenn nicht, dann scheint er probleme mit funktionszeigern als template parameter zu haben...
Sehe ich auch so. Das selbe passiert übrigens auch bei Funktionen:
void Func() { cout << "Func" << endl; } template < void (*callback)() > struct Helper {}; template < void (*callback)() > void Add(Helper<callback> ) { callback(); } int main() { Add(Helper<&Func>()); }
Verzichtet man im letzten Beispiel auf diesen Workaround und schreibt es VC6.0 typisch, dann funktioniert's:
template < void (*callback)() > void Add(Helper<callback>* = 0) { callback(); } int main() { Add<&Func>(); }
Umgehen lässt sich das Problem, wenn man die ganzen Geschichten mit dem non-type-Parameter in eine Klasse packt. Und später ein Objekt dieser Klasse dann als type-Parameter an Add übergibt.
Das Original würde so aussehen:
template </*...*/> class Foo { public: template <class SomeLhs, class SomeRhs, ResultType (*callback)(SomeLhs&, SomeRhs&)> void Add() { // Dieser typedef Ausdruck ist entscheidend. // Hier muss callback ein *konstanter Wert* sein, der an den // non-type-Parameter von FnDispatcherHelper übergeben wird. typedef Private::FnDispatcherHelper< BaseLhs, BaseRhs, SomeLhs, SomeRhs, ResultType, CastingPolicy<SomeLhs,BaseLhs>, CastingPolicy<SomeRhs,BaseRhs>, callback> Local; // Ruft eine ander Add-Methode der Klasse auf. Ist aber nicht wichtig. Add<SomeLhs, SomeRhs>(&Local::Trampoline); } };
Ich habe bisher zwei verschiedene Workarounds:
template </*...*/> class Foo { public: template <class SomeLhs, class SomeRhs, ResultType (*callback)(SomeLhs&, SomeRhs&)> struct EtasHelper { typedef Private::FnDispatcherHelper< BaseLhs, BaseRhs, SomeLhs, SomeRhs, ResultType, ApplyInnerType2<CastingPolicy, SomeLhs,BaseLhs>::type, ApplyInnerType2<CastingPolicy,SomeRhs,BaseRhs>::type, callback> Local; typedef SomeLhs Lhs; typedef SomeRhs Rhs; }; template <class EtasType> void Add(EtasType EtasObj) { typedef typename EtasType::Local Local; typedef typename EtasType::Lhs SomeLhs; typedef typename EtasType::Rhs SomeRhs; Add(&Local::Trampoline, ::Loki::Type2Type<SomeLhs>(), ::Loki::Type2Type<SomeRhs>()); } };
Dieser verändert die Schnittstelle in:
typedef Foo<...> FooType; FooType f; // Original f.Add<Poly, Poly, &Func>(); // Workaround1 f.Add(FooType::EtasHelper<Poly, Poly, &Func>());
Der zweite Workaround sieht so aus:
template </*...*/> class Foo { public: // Statt der Add-Methode eine innere Template-Klasse // mit überladenem operator(). template <class SomeLhs, class SomeRhs, ResultType (*callback)(SomeLhs&, SomeRhs&), bool symmetric = false> struct AddI { void operator()(Foo<...>& f) { typedef Private::FnDispatcherHelper< BaseLhs, BaseRhs, SomeLhs, SomeRhs, ResultType, ApplyInnerType2<CastingPolicy, SomeLhs,BaseLhs>::type, ApplyInnerType2<CastingPolicy,SomeRhs,BaseRhs>::type, callback> Local; // Wir brauchen das Foo-Objekt hier f.Add(&Local::Trampoline, ::Loki::Type2Type<SomeLhs>(), ::Loki::Type2Type<SomeRhs>()); } }; };
Der Aufruf:
typedef Foo<...> FooType; FooType f; // Workaround2 FooType::AddI<Poly,Poly,&Func>()(f);
Der erste Workaround ist in der Benutzung konsistenter zum restlichen Port. Der zweite ist etwas kürzer.
Welcher gefällt euch besser?
Hat jemand eine andere geniale Idee?Achso, EtasHelper steht übrigens für explicit template argument specification Helper. Das war mir aber irgendwie zu lang
[ Dieser Beitrag wurde am 21.03.2003 um 11:14 Uhr von HumeSikkins editiert. ]
-
Welcher gefällt euch besser?
Hat jemand eine andere geniale Idee?Nr.1 finde ich besser
Nr.2 mache statt den op() ein template ctor, da durch kanns du die einen () weglassen (wird aber etwas umständlich zu erklären in der readme)
-
Nr.2 mache statt den op() ein template ctor, da durch kanns du die einen () weglassen (wird aber etwas umständlich zu erklären in der readme)
Nein. Das geht leider nicht.
class Foo { public: struct Add { template <class T> Add(T o) {} }; }; int main() { Foo f; // Diese Zeile macht nicht das was man meinen könnte. Hier // wird kein unbenanntes Objekt vom Typ Foo::Add angelegt, dessen // Ctor mit dem Parameter f augerufen wird. // Vielmehr wird hier ein Objekt f vom Typ Foo::Add // erzeugt. Und zwar durch den Aufruf des Standardctors. // Die Zeile hat also zwei Fehler: // 1. Add hat keinen Standard-Ctor // 2. f wurde bereits definiert Foo::Add(f); }
Man müsste also sowas wie:
int main() { Foo f; Foo::Add((Foo)f); }
schreiben. Das ist aber auch nicht besser als ein zweites Paar Klammern.
[ Dieser Beitrag wurde am 21.03.2003 um 13:30 Uhr von HumeSikkins editiert. ]