Template-Verwirrung

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
FlorianB82
Beiträge: 70
Registriert: 18.11.2010, 05:08
Wohnort: Darmstadt
Kontaktdaten:

Template-Verwirrung

Beitrag von FlorianB82 »

Hallo zusammen,

ich hätte hier mal wieder ein etwas spezielles Problem in C++, auf das mir mangels Erfahrung mit Templates gerade keine schöne Lösung einfallen mag.

Gegeben sei:

Code: Alles auswählen

class InjectInterface { ... };

class Inject1: public InjectInterface {};


class WorkInterface { ... };

class WorkA: public WorkInterface
{
	typedef enum {MODE_1, MODE_2, ... , MODE_N} mode_t;
	typedef Inject1 inject_t; // 1..n möglich

	WorkA(mode_t mode, inject_t *inject) { ... };
};
Analog zu Inject1 gibt es noch N weitere; und analog zu WorkA gibt es noch X weitere. Jedes konkrete WorkX benötigt ein spezielles InjectN.

Alle WorkX werden an einer Stelle anhand eines von außen gegebenen Parameters erzeugt:

Code: Alles auswählen

InjectInterface *i;
WorkInterface *w;

switch (outerParameter)
{
    case MAGIC_VALUE_1: i = WorkA::inject_t(); w = new WorkA(MODE_1, i); break;
    case MAGIC_VALUE_2: i = WorkA::inject_t(); w = new WorkA(MODE_2, i); break;
	... // alle möglichen Instantiierungen von WorkX mit verschiedenen InjectN
}
Diesen Switch finde ich aber hässlich:
  • Weil ich mir mit redundantem Sch... die Finger wundtippe.
  • Weil ich an anderer Stelle einen ähnlichen Switch habe, der mir für jeden MAGIG_VALUE sagt, ob dazu ein WorkX/InjectN-Paar gebaut werden kann. Schöner wäre ein Switch, der mir Funktionszeiger auf etwas gibt, mit dem ich das WorkX/InjectN-Paar bauen kann. Den kann ich dann sowohl zur Existenzbestimmung als auch zur Erzeugung nutzen.
Zunächst habe ich es mit einem Macro (BÄÄÄH!) versucht - das hat zwar Punkt 1 gelöst, Punkt 2 aber nicht. Und dafür mit allen Nachteilen, die ein Makro hat.

Dann habe ich ein Funktionstemplate gebaut:

Code: Alles auswählen

template<T> void create(T::mode_t mode)
{
    i = new T::inject_t();
    w = new T(mode, static_cast<T::inject_t*>(i));
}

// Verwendung:
switch (outerParameter)
{
    case MAGIC_VALUE_1: create<WorkA>(WorkA::MODE1); break;
    ...
}
Ebenfalls Punkt1 gelöst, und das Makro ist weg. Die Lösung von Punkt 2 ist näher, da ich jetzt etwas habe, was eine Adresse besitzt. Aber so richtig verwenden kann ichs nicht.
Dafür muss ich jetzt wieder den Typnamen wiederholen, weil der Compiler nur den expliziten Template-Aufruf hinbekommt. Im impliziten Fall kann er den Typ T nicht deduzieren. Wieso eigentlich? Promoted der enum zu einem int, und es wird nach einer Klasse gesucht, die irgendwas int-kompatibles als nested Type enthält? Und wie muss ich das Template denn aufbohren, damit ich es implizit (create(WorkA::MODE1)) aufrufen kann? Geht das überhaupt? Anscheinend ist WorkA::MODE1 nicht eindeutig genug, um zu erkennen, dass T WorkA sein muss?

Und die andere Frage: Kann ich diese Template-Funktionen so bauen, dass ich sie ganz generisch über einen Pointer aufrufen kann? Also soetwas in der Art:

Code: Alles auswählen

CreationFunc* getCreator(magic_value_t t)
{
    switch (t)
    {
        case MAGIC_VALUE_1: return &(someTemplateFunc<WorkA::MODE1>);
        ...
    }
}

// Aufruf:
CreationFunc* f = getCreator(MAGIC_VALUE_1);
f(); // i und w sind jetzt mit Zeigern auf gültige Instanzen gefüllt
bool canCreate = (NULL != getCreator(MAGIC_VALUE_1)); // Existenzüberprüfung
Sorry, wenn das jetzt etwas wirr aussieht. Aber eventuell kennt jemand eine schöne Lösung dafür?
Benutzeravatar
FlorianB82
Beiträge: 70
Registriert: 18.11.2010, 05:08
Wohnort: Darmstadt
Kontaktdaten:

Re: Template-Verwirrung

Beitrag von FlorianB82 »

So, da das Thema mich nicht losgelassen hat und ich noch keine Antworten erhalten habe, habe ich habe jetzt mal ein wenig recherchiert, und bin dabei auf folgendes gekommen. Hoffentlich dient es auch anderen zur Information, denn sonst könnte ich mir mein Geschreibsel sparen ;)
  • Der Grund, warum die Template Deduction scheitert, ist die Tatsache, dass der Typ T::mode_t im Template template<T> void create(T::mode_t mode) non-deducable ist. Siehe auch hier und hier. Mit einfachen Worten erklärt bedeutet dies, dass der Compiler leider nicht vom Funktionsparametertyp WorkA::mode_t auf den Templateparametertyp WorkA schließen kann, denn es könnte ja noch andere potentiell unendlich viele T geben, deren innerer mode_t derselbe wie der von WorkA ist.

    Damit kann ich leider vergessen, das richtige Template nur über den Parameter WorkX::mode_t auszuwählen. Verwende ich dagegen den gesamten Typ WorkX::mode_t als Template-Typ, so kann ich zwar damit das richtige Template auswählen (das mir nun für jede WorkX/Mode N-Kombination eine Funktion liefert), aber leider bekomme ich dann kein T und somit erst recht kein T::inject_t daraus bestimmt (oder gibts für sowas abgefahrene C++11-Funktionen?).

    Unterm Strich also Arschkarte, und ich muss die Templates explizit aufrufen. Eventuell geht vielleicht noch was mit etwas elaborierterem TMP, aber das wird mir dann zu abgefahren, und macht die Lösung eher wieder hässlicher, als dass ich etwas damit gewinne.
  • Das Problem mit den Funktionszeigern lässt sich meiner Meinung aber recht leicht lösen: Wenn ich ein Funktionstemplate erstelle, das keine Funktionsparameter aufweist, kann ich dessen Instantiierungen alle über den gleichen Funktionspointertyp aufrufen. Der mode-Parameter muss dazu nur aus der Funktionsparameterliste in die Templateparameterliste in Form eines Non-Type-Parameters wandern. Damit erhalte ich für jede WorkX/Mode N-Kombination eine Funktion, und nicht mehr wie vorher für jede WorkX-Klasse. Dafür sind die generierten Funktionen parameterlos und haben den jeweiligen Mode schon fest eingebaut.

    Aussehen würde das dann so:

    Code: Alles auswählen

    // Funktionstemplate
    template<T, T::mode_t mode> void create()
    {
        i = new T::inject_t();
        w = new T(mode, static_cast<T::inject_t*>(i));
    }
    
    // Zeigertyp, der auf die erzeugten Funktionen zeigen kann
    typedef void (*funcptr_t)();
    
    // Ermittlung des zum magic value passenden Funktionszeigers
    funcptr_t getCreator(magic_value_t t)
    {
        switch (t)
        {
            case MAGIC_VALUE_1: return &(create<WorkA, WorkA::MODE1>);
            ...
        }
    }
Antworten