PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : C++ template-funktion: spezialisierung für klassenhierarchie


Chris Lux
2004-12-21, 10:38:34
folgendes problem:

#include <iostream>
#include <string>

struct base
{
base(const std::string& name)
: _name(name){}

std::string _name;
};


struct deriv1 : public base
{
deriv1(const std::string& name) : base(name)
{
}
};

struct deriv2 : public base
{
deriv2(const std::string& name) : base(name)
{
}
};

template<typename T> void func(T ref)
{
std::cout << ref << std::endl;
}

template<> void func<base*>(base* ref)
{
std::cout << ref->_name << std::endl;
}


int main()
{
deriv1 t1("t1");
deriv2 t2("t2");

float a = 1.0;
unsigned b = 2;

func(&t1);
func(&t2);
func(a);
func(b);


return (0);
}


func wurde für pointer auf basisklassen spezialisiert. laut stroustrup (kapitel 13.5) sollte man spezialisierung nutzen können, um ausdrücken zu können, dass die spezielisierung für alle klassen, welche von base abgeleitet sind genutzt werden soll.

problem, ES GEHT NICHT. probiert habe ich auch folgende versionen:

template<typename T> void func(const T& ref)
{
std::cout << ref << std::endl;
}

template<> void func<base>(const base& ref)
{
std::cout << ref._name << std::endl;
}


int main()
{
deriv1 t1("t1");
deriv2 t2("t2");

float a = 1.0;
unsigned b = 2;

func(t1);
func(t2);
func(a);
func(b);


return (0);
}


es wird jedesmal versucht die allgemeine func für die derivate zu compilieren.
gibt es da einen trick, den ich noch nicht kenne, um solche problemstellungen ausdrücken zu können?

compiler version: MS VS C++ 7.1

Edit:
folgendes funktioniert auch NICHT (war auf den verdacht hin, dass die structs probleme machen):

#include <iostream>
#include <string>

class base
{
public:
base(const std::string& name)
: _name(name){}

std::string _name;
};


class deriv1 : public base
{
public:
deriv1(const std::string& name) : base(name)
{
}
};

class deriv2 : public base
{
public:
deriv2(const std::string& name) : base(name)
{
}
};

template<class T> void func(const T* ref)
{
std::cout << *ref << std::endl;
}

template<> void func<class base>(const base* ref)
{
std::cout << ref->_name << std::endl;
}


int main()
{
deriv1 t1("t1");
deriv2 t2("t2");

float a = 1.0;
unsigned b = 2;

func(&t1);
func(&t2);
func(&a);
func(&b);


return (0);
}

KiBa
2004-12-21, 12:02:10
moin, lux... ;)
templates und vererbung sind in den meisten fällen nicht verträglich. wie auch hier, da der compiler nur anhand der signatur die entsprechende funktion auswählt.
nicht nur stroustrup, sondern auch scott meyers betonen immer wieder die probleme, die beim gleichzeitigen einsatz dieser zwei techniken auftreten...

dein problem lässt sich auf verschiedene weisen lösen, je nach komplexität. das einfachste wäre wohl ein überladener << operator. oder vielleicht stinknormale polymorphie, also eine virtuelle getName() methode in base. func bräuchte dann nicht mehr spezialisiert, sondern einfach nur für base überladen werden.
auch traits-klassen würde ich in betracht ziehen, welche verschiedene typmerkmale kapseln und für alle möglichen typen spezialisiert werden kann, vorzugsweise mit partieller spezialisierung. die standardlibs von c++ sind ja voll mit beispielen...

Chris Lux
2004-12-21, 12:10:12
templates und vererbung sind in den meisten fällen nicht verträglich. wie auch hier, da der compiler nur anhand der signatur die entsprechende funktion auswählt.
nicht nur stroustrup, sondern auch scott meyers betonen immer wieder die probleme, die beim gleichzeitigen einsatz dieser zwei techniken auftreten...

dein problem lässt sich auf verschiedene weisen lösen, je nach komplexität. [...]

hehe irgendwie wusst ich, dass du antwortest (hab hier leider kein icq ;))

der code oben soll nur das problem zeigen, das wirkliche problem is ne ecke komplexer, weshalb ich deine lösungen leider schon im vorfeld bedacht und verworfen habe.

im stoustrup steht bei der kapitel 13.5 einführung folgendes:
... oder >>Liefere einen Fehler, falls das Template-Argument kein Zeiger auf einevon My_base abgeleitete Klasse ist.<<
im zusammenhang mit dem nutzen von template spezialisierung. ich kenne die probleme mit templates und runtime-polymorphismus zur genüge, nur sollte auch zur compilezeit erkennbar sein, das derivat klassen von base abgeleitet wurden und welche spezialisierung passend ist.

könnte dieses beispiel jemand auf einem anderen compiler testen, so dass ich sicher sein kann, dass dies kein bug im VS compiler ist.

KiBa
2004-12-21, 13:15:08
template<typename T> void func(const T& ref)
passt perfekt zu den derivaten, besser als
template<> void func<base>(const base& ref)
da T direkt mit deriv1 oder deriv2 instantiiert werden kann. der compiler schaut dabei nicht auf die implementierung, wie bei allen anderen funktionen auch, wo meist nur die signatur der headerdatei bekannt ist.

das funktioniert so wie du das willst nicht...
das problem von templates ist nicht nur im zusammenhang mit runtime-polymorphismus gegeben, sondern auch allgemein mit vererbung, wie dieses beispiel schön demonstriert.

weitere lösungsvorschläge kanns natürlich nur mit genauerer problembeschreibung geben... ;)

Chris Lux
2004-12-21, 13:31:13
template<typename T> void func(const T& ref)
passt perfekt zu den derivaten, besser als
template<> void func<base>(const base& ref)
da T direkt mit deriv1 oder deriv2 instantiiert werden kann. der compiler schaut dabei nicht auf die implementierung, wie bei allen anderen funktionen auch, wo meist nur die signatur der headerdatei bekannt ist.

is ja klar, dass die allgemeinste funktion am besten passt ;), aber spezialisierungen sind ja dafür da ausnahmen zu schaffen. es müssen ja die spezialisierungen der allgemeinheit nach geordnet sein. folgendes geht ja auch:


template<typename T> void func(...);
template<typename T> void func<T*>(...);
template<> void func(void*)(...);


hier passt auch T* zu jedem zeiger besser, doch für den speziellen void* wird trotzdem die letzte genommen. im grunde ist mein problem nichts anderes, nur dass der compiler nicht merkt, dass derivat von base abgeleitet ist.

Trap
2004-12-21, 13:56:57
Wenn du den Pointer explizit zum (base*) castest sollte es funktionieren.

Der Compiler guckt erst ob es ein passendes Template gibt, bevor er es für kompatible Typen probiert.

Chris Lux
2004-12-21, 14:22:04
Wenn du den Pointer explizit zum (base*) castest sollte es funktionieren.

Der Compiler guckt erst ob es ein passendes Template gibt, bevor er es für kompatible Typen probiert.

hmm, das wirkt genau dem nutzen entgegen, den ich erreichen will. ich könnte genauso auch schreiben func<base*>(derivat); aber so könnte ich auch gleich eine zweite funktion mit anderem namen nutzen.

KiBa
2004-12-21, 14:40:53
hier passt auch T* zu jedem zeiger besser, doch für den speziellen void* wird trotzdem die letzte genommen. im grunde ist mein problem nichts anderes, nur dass der compiler nicht merkt, dass derivat von base abgeleitet ist.
eben, im beispiel hast du void* explizit spezialisiert. also musst du analog für dein problem jedes derivat spezialisieren. der aufwand hierzu hält sich in grenzen, da für jede neue derivat-klasse eine funktion extra geschrieben werden muss...
weniger aufwand wären spezialisierungen für alle eingebauten typen und eine einfache überladung für base.
eigentlich willst du dir mit dieser konstruktion doch nur tipparbeit ersparen, und den compiler mehr arbeiten lassen, da zur compilezeit ja alles schon feststehen muss. d.h. in dem moment, wo du func aufrufst, kennst du doch den genauen typen und kannst func damit instanzieren... ich sehe hier das problem nicht z.b. func<float>(2.0f) zu schreiben. hat bis auf ein paar zeichen mehr zu tippen keine nachteile...

Trap
2004-12-21, 14:43:47
Eine sehr hässliche Lösung:
template<> void func<deriv1*>(deriv1* ref)
{
func((base*)ref);
}

template<> void func<deriv2*>(deriv2* ref)
{
func((base*)ref);
}

Chris Lux
2004-12-21, 15:12:59
[...]ich sehe hier das problem nicht z.b. func<float>(2.0f) zu schreiben. hat bis auf ein paar zeichen mehr zu tippen keine nachteile...

das problem ist, dass ich für einen (bisher fiktiven) anderen entwickler eine masse an komplexität verstecken will/muss. er soll nur mit allgemeinen funktionen arbeiten. ein anderer (auch bisher fiktiver) entwickler sollte in zukunft meinen code mit wenig aufwand um neue abarten (derivate) erweitern können. dazu sind die template funktionen da, sie arbeiten mit den an einer stelle spezifizierten derivaten ohne, dass an tausend stellen änderungen durchgeführt weren müssen.

so sieht es aus und zZ such ich halt lösungen für diese probleme.

del_4901
2004-12-21, 21:40:54
warum hast du eigendl. kein

#using namespace std;

einige compiler verlangen danach. (den neuen VS hab ich ned.. kann ned testen)

und warum hast du func überladen?

verwirsst du den compiler nicht indem du einmal ein func mit der baseklasse hast und einmal mit einem Kind, ich mein das Kind ist für den Compiler doch wie die basisklasse, dann weiß er doch wieder nicht welches func er nehmen soll.

Chris Lux
2004-12-22, 08:00:18
warum hast du eigendl. kein
#using namespace std;
einige compiler verlangen danach. (den neuen VS hab ich ned.. kann ned testen)

weil es nicht gebraucht wird. wenn dein compiler danach verlangt ist da was nicht richtig. mit der using direktive fügst du alle symbole des namespaces in den (hier) globalen namespace ein, was gegebenenfalls zu namenskolissionen führen kann. mit dem scope operator :: kann man ja speziell den gewünschten namespace auflösen, was IMO immer der bessere weg ist. wenn du in speziellen scopes (wie funktionen) bestimmte symbole automatisch aufgelöst haben möchtest kannst du das auch tun, zB:
void func(...)
{
using std::string;
using std::map;

map<string, int> _blah;
}
und hier wurden nicht _alle_ symbole aus std in den scope eingeführt.


und warum hast du func überladen?

verwirsst du den compiler nicht indem du einmal ein func mit der baseklasse hast und einmal mit einem Kind, ich mein das Kind ist für den Compiler doch wie die basisklasse, dann weiß er doch wieder nicht welches func er nehmen soll.

das ist kein überladen, das ist template spezialisierung. die allgemeine func führt im grunde andere operationen auf den für sie passenden typen aus. für meine hierarchie sollen halt andere/angepasste operationen ausgeführt werden, deshalb die spezialisierung.

del_4901
2004-12-22, 08:46:25
mit dem scope operator :: kann man ja speziell den gewünschten namespace auflösen, was IMO immer der bessere weg ist.


i know.
der GCC meckert trotzdem machmal ... naja dreckscompiler


das ist kein überladen, das ist template spezialisierung. die allgemeine func führt im grunde andere operationen auf den für sie passenden typen aus. für meine hierarchie sollen halt andere/angepasste operationen ausgeführt werden, deshalb die spezialisierung.

Solche template spezialisierungen sind mir neu. Soweit wie ich weiß macht der compiler bei templates eine stupide Textersetzung, das tolle daran ist das die Sache Typsicher bleibt (nicht wie beim Präprozessor).

BTW: Bist du sicher das "template<>" da hingehört? Hat das vllt was mit der Spezialisierung zu tun? ich würd's vllt mal ohne probiern.

BTW2: versuch mal die Templateparameter der funktion beim Aufruf mit zu übergeben (eigendlich ist das bei dieser Anwendung von templates Blödsinn, aber der Versuch macht Kluch)

BTW3: Wo genau meckert denn der Compiler, oder meckert der Linker?

Chris Lux
2004-12-22, 09:22:05
i know.
Solche template spezialisierungen sind mir neu. Soweit wie ich weiß macht der compiler bei templates eine stupide Textersetzung, das tolle daran ist das die Sache Typsicher bleibt (nicht wie beim Präprozessor).

normalerweise macht er noch eine typprüfung, ob dieser typ da rein passt (kann mich da aber irren).

BTW: Bist du sicher das "template<>" da hingehört? Hat das vllt was mit der Spezialisierung zu tun? ich würd's vllt mal ohne probiern.

syntaktisch ist das mit <> richtiger. man kann es aber weglassen bei einer vollständigen spezialisierung (dh für einen genauen typ).

BTW2: versuch mal die Templateparameter der funktion beim Aufruf mit zu übergeben (eigendlich ist das bei dieser Anwendung von templates Blödsinn, aber der Versuch macht Kluch)

jup da funktioniert es, jedoch ist das genau das was ich verhindern möchte.

BTW3: Wo genau meckert denn der Compiler, oder meckert der Linker?
der compiler meckert, weil er versucht die derivate in das allgemeine template einzubauen, was dann wieder bei den operationen in der funktion schief geht (welche nicht zu den derivat typen passen, weshalb auch die spezialisierung her sollte).

del_4901
2004-12-22, 09:40:58
normalerweise macht er noch eine typprüfung, ob dieser typ da rein passt (kann mich da aber irren).

das heißt doch Typsicher, also du irrst nicht.

BTW: der Compiler oder besser gesagt der Linker baut nur die Templates in den Code ein die auch verwendet werden, sprich eine Templatefunktion die nie verwendet wird, braucht auch keinen Platz im Codesegment.


der compiler meckert, weil er versucht die derivate in das allgemeine template einzubauen, was dann wieder bei den operationen in der funktion schief geht (welche nicht zu den derivat typen passen, weshalb auch die spezialisierung her sollte).

Wie gesagt ich kenn mich mit den Speziallisierungen nicht so genau aus, aber vllt. kannst du das "<hier steht was>" hinter func in der Speziallisierung entsorgen.

Und meine zweite Idee ist, forwarde mal die die funktionen, manchmal hat der Parser ein Problem.

Chris Lux
2004-12-22, 12:00:06
Wie gesagt ich kenn mich mit den Speziallisierungen nicht so genau aus, aber vllt. kannst du das "<hier steht was>" hinter func in der Speziallisierung entsorgen.

Und meine zweite Idee ist, forwarde mal die die funktionen, manchmal hat der Parser ein Problem.

erste idee ist wieder das syntaktisch korrekte, funktioniert aber auch ohne das <...>.
zweite idee hab ich auch schon probiert, leider erfolglos.

ich hab mich jez schon zu ner anderen lösung des eigentlichen problems entschieden. wäre trotzdem mal schön zu wissen ob das nun prinzipiell gehen sollte oder nicht.

del_4901
2004-12-22, 12:17:00
erste idee ist wieder das syntaktisch korrekte, funktioniert aber auch ohne das <...>.
zweite idee hab ich auch schon probiert, leider erfolglos.

ich hab mich jez schon zu ner anderen lösung des eigentlichen problems entschieden. wäre trotzdem mal schön zu wissen ob das nun prinzipiell gehen sollte oder nicht.


deine Lsg. würde mich interessieren, wenn du gewíllt bist sie rauszugeben.

KiBa
2004-12-22, 16:46:20
ich hab mich jez schon zu ner anderen lösung des eigentlichen problems entschieden. wäre trotzdem mal schön zu wissen ob das nun prinzipiell gehen sollte oder nicht.

definitiv nicht. laut standard muss die template-version ohne spezialisierung ausgewählt werden, eben weil sie besser passt (unabhängig davon, ob sie überhaupt kompiliert). bei template im zusammenhang mit vererbung sollten immer die alarmglocken angehen, das funktioniert quasi nie wie man sich das vorstellt...

mit tricks gehts aber trotzdem ohne viel tipparbeit... ;)
in boost existieren meta-funktionen, welche rausbekommen können, ob ein typ ein einfaches eingebautes pod (plain old data) ist. man kann auch gleitkommazahlen oder integrale typen extra mit anderen metafunktionen behandeln. für diese kannst du ein template von func schreiben, welche das object direkt auf den stream ausgeben (oder was anderes machen, z.b. das object selber zurückgeben). für alle anderen fälle rufst du halt den member _name des objekts auf (oder gibst ihn direkt zurück).
so kann man den zugriff auf eingebaute typen und derivaten deiner base klasse vereinheitlichen. fügst du neue derivate hinzu, brauchst du dann nix weiter zu machen.