template in template?? Wie und warum?



  • Hallo leute,

    habe grad mal das ausprobiert:

    template<template<typename T> typename U>
    class bla
    {
    
    };
    

    Fehler: Template Argumente müssen eine Klasse bezeichnen

    Also wie muss es richtig lauten?

    Aber viel wichtiger ist die frage, für was braucht man templates in templates wirklich sinnvoll?

    Kann mir vielleicht jmd mal ein gut verständliches beispiel geben?



  • hier ein kleines beispiel:

    #include <iostream>
    #include <list>
    using namespace std;
    
    template<typename T>
    struct Summierer
    {
    	template<template<typename> class TContainer>
    	T operator()(const TContainer<T> &container)
    	{
    		T ergebnis = 0;
    		for(typename TContainer<T>::const_iterator iter = container.begin(); iter!=container.end(); ++iter)
    			ergebnis += (*iter);
    		return ergebnis;
    	}
    };
    
    int main()
    {
    	Summierer<int> summiere;
    	list<int> test;
    	test.push_back(32);
    	test.push_back(8);
    	test.push_back(10);
    
    	cout << summiere(test) << endl;
    	cin.get();
    }
    


  • wenn man nur STL-Container erwartet, kann man es natürlich auch so machen:

    #include <iostream>
    #include <list>
    using namespace std;
    
    struct Summierer
    {
        template<typename TContainer>
        typename TContainer::value_type operator()(const TContainer &container)
        {
            typename TContainer::value_type ergebnis = 0;
            for(typename TContainer::const_iterator iter = container.begin(); iter!=container.end(); ++iter)
                ergebnis += (*iter);
            return ergebnis;
        }
    };
    
    int main()
    {
        Summierer summiere;
        list<int> test;
        test.push_back(32);
        test.push_back(8);
        test.push_back(10);
    
        cout << summiere(test) << endl;
        cin.get();
    }
    


  • @schorsch, was hat das jetzt mit template-templates zu tun?



  • in der ersten version werden template-templates benutzt. dort wird festgelegt, dass als template nur template-typen mit einem bestimmten typ als parameter zugelassen sind. die instanz von Summierer<int> zum beispiel, nimmt nur template-typen, die int als template-typ haben, als parameter.
    die zweite version benutzt hingegen keine template-templates. dort können alle möglichen typen (nicht nur template-typen mit festgelegem template-typ) als template-typ verwendet werden, sofern die schnittstellen unterstützt werden.



  • hier noch mal zur veranschaulichung:

    #include <iostream>
    #include <list>
    using namespace std;
    
    template<typename T>
    struct Summierer
    {
        template<template<typename> class TContainer>
        T operator()(const TContainer<T> &container)
        {
            T ergebnis = T();
            for(typename TContainer<T>::const_iterator iter = container.begin(); iter!=container.end(); ++iter)
                ergebnis += (*iter);
            return ergebnis;
        }
    };
    
    int main()
    {
        Summierer<int> summiereNurTemplateTypenMitIntAlsTemplateTyp;
        Summierer<double> summiereNurTemplateTypenMitDoubleAlsTemplateTyp;
    
        list<int> test;
        test.push_back(32);
        test.push_back(8);
        test.push_back(10);
    
    	//ok! list<int> als Template-Typ mit int als Template-Typ!
        cout << summiereNurTemplateTypenMitIntAlsTemplateTyp(test) << endl;
    
        //folgendes geht nicht!!! Template-Typen mit double als Template-Typ sind gefordert!
        //cout << summiereNurTemplateTypenMitDoubleAlsTemplateTyp(test) << endl;
    
        cin.get();
    }
    


  • Doof nur, dass ein Standard-Container kein gültiges Argument für eine Template-Funktion ist, die als Parameter ein Template vom Typ const TContainer<T> erwartet. Template-Template-Parameter müssen immer exakt passen. Default-Werte werden ignoriert. Demzufolge müsste deine Template-Template-Parameter zwei Template-Parameter unterstützen - der zweite für den Allocator-Type. Das wäre aber auch nicht portabel, da der Standard die genaue Anzahl der Template-Parameter für die Standard-Container nicht festlegt. Jeder Standard-Bibliothek kann also beliebig viele weitere Template-Parameter hinzufügen (solange diese alle Default-Werte haben).



  • Oh 😮 Dass Default-Werte für Template-Parameter ignoriert werden, wusste ich nicht. Template-Template-Parameter in direkter Verbindung mit Standard-Containern zu benutzen macht dann wohl wenig Sinn. Ein (ziemlich hässlicher) Workaround dafür, wäre natürlich eine Ableitung zu benutzen, wo die zusätzlichen Template-Parameter fest verdrahtet sind.



  • Eine bessere Lösung ist es wohl, einen beliebigen Container zu erwarten (hat auch den Vorteil, daß du damit eine list<float> durch einen Summierer<int> jagen könntest ;)).

    PS: Und noch eine Option: Die STL bietet auch Algorithmen an - z.B. accumulate(), der hier wohl ideal einzusetzen wäre.



  • Ist ja schon gut 😞 Das war ein blödes, weil praxisfernes, Beispiel, aber ein paar Eigenheiten von Template-Templates sind bei rumgekommen. Die Vorteile wurden wahrscheinlich nicht so richtig klar, deshalb hier ein anderes Beispiel:

    #include <iostream>
    using namespace std;
    
    class Wurst
    {
    public:
    	static const char *produkt() { return "Wurst"; }
    };
    
    class Gehacktes
    {
    public:
    	static const char *produkt() { return "Gehacktes"; }
    };
    
    template<typename TProdukt>
    class SchweineMetzger
    {
    public:
    	static void produkt() { cout << "Schweine-" << TProdukt::produkt() << endl; }
    };
    
    template<typename TProdukt>
    class RinderMetzger
    {
    public:
    	static void produkt() { cout << "Rinder-" << TProdukt::produkt() << endl; }
    };
    
    template<template<typename> class TMetzger>
    class Metzgerei
    {
    public:
        static void wurst() { TMetzger<Wurst>::produkt(); }
        static void gehacktes() { TMetzger<Gehacktes>::produkt(); }
    };
    
    int main()
    {
    	Metzgerei<SchweineMetzger>::wurst();
    	Metzgerei<SchweineMetzger>::gehacktes();
    	Metzgerei<RinderMetzger>::wurst();
    	Metzgerei<RinderMetzger>::gehacktes();
    
        cin.get();
    }
    

    Die Metzgerei nimmt einen Metzger als Template-Template-Parameter. Dieser kann dann von der Metzgerei mit Produkten konfiguriert werden, indem sie ihm als Template-Parameter übergeben werden.



  • Wobei es natürlich lässiger ist, wenn man nicht für jedes neue Produkt eine neue Methode in der Metzgerei braucht:

    //...
    template<template<typename> class TMetzger>
    class Metzgerei
    {
    public:
        template<typename TProdukt>
        static void produkt() { TMetzger<TProdukt>::produkt(); }
    };
    
    int main()
    {
        Metzgerei<SchweineMetzger>::produkt<Wurst>();
        Metzgerei<SchweineMetzger>::produkt<Gehacktes>();
        Metzgerei<RinderMetzger>::produkt<Wurst>();
        Metzgerei<RinderMetzger>::produkt<Gehacktes>();
    
        cin.get();
    }
    

Anmelden zum Antworten