Methodenüberladung in Template-Klassen



  • Hallo,

    ich habe zwei Template-Klassen

    template<class N> class A
    {
    public:
    	virtual vector<N*> unspec_nodes() const = 0;
    	...
    };
    
    template<class N, class ON> class B : public virtual A<N>
    {
    public:
    	vector<ON*> nodes() const;
    	vector<N*> unspec_nodes() const;
    	...
    };
    

    Das Problem ist nun, dass ich in der Klasse B

    • eine Methode nodes() habe, die bestimmte Knoten zurückgibt, und
    • eine Methode unspec_nodes(), die dieselben Knoten zurückgibt, aber als unspezifische Knoten (ON ist aus N abgeleitet).

    Ich frage mich nun, ob man die Benennung der Methoden konsistenter machen kann, dass ich statt

    B<C_N, C_ON> b;
    b->nodes();
    b->unspec_nodes()
    

    irgendwas verständlicheres in der Richtung

    B<C_N, C_ON> b;
    b->nodes<C_N>();
    b->nodes<C_ON>();
    

    schreiben kann.

    Was ist eurer Meinung, die beste Technik hierfür?

    Anmerkung: Diese Anfrage ist eine Fortsetzung des Beitrags http://www.c-plusplus.net/forum/277511 , der inzwischen etwas unübersichtlich ist und daher als neuer Beitrag fortgesetzt wird.



  • Das kannst du mit Spezialisierung lösen.

    C_N und C_ON sind dann einfach Parameter für Spezialisierungen der gleichen Funktion nodes .



  • drakon schrieb:

    Das kannst du mit Spezialisierung lösen.

    C_N und C_ON sind dann einfach Parameter für Spezialisierungen der gleichen Funktion nodes .

    Ich habe das versucht wie folgt umzusetzen (gibt aber Fehler beim kompilieren):

    template<class N> class A
    {
    public:
        virtual vector<N*> nodes() const = 0;
    };
    
    template<class N, class ON> class B : public virtual A<N>
    {
    public:
        template <class R> vector<R*> nodes() const;
    };
    
    template<class N, class ON> template<> vector<N*> B<N, ON>::nodes() const
    { }   // Rückgabe musste noch erzeugt und zurückgegeben werden
    
    template<class N, class ON> template<> vector<ON*> B<N, ON>::nodes() const
    { }   // Rückgabe musste noch erzeugt und zurückgegeben werden
    

    Die Fehlermeldung lautet:

    ../src/scratch.h:15: error: invalid explicit specialization before ‘>’
    token
    ../src/scratch.h:15: error: enclosing class templates are not explicitly
        specialized
    ../src/scratch.h:15: confused by earlier errors, bailing out
    

    Kann mir jemand sagen, wie man das richtig macht?



  • Member-Funktionen kann man soweit ich weiss nicht spezialisieren.

    Du kannst den Umweg über eine Helper-Klasse mit statischen Funktionen gehen wenn du unbedingt willst. Ist aber in der Konstellation vermutlich etwas ... "hässlich".

    Oder du kannst etwas wie boost::type<> und ganz normale Overloads verwenden:

    class foo
    {
    public:
        void bar(boost::type<baz>);
        void bar(boost::type<qux>);
    };
    
    void test()
    {
        foo f;
        f.bar(boost::type<baz>());
        f.bar(boost::type<qux>());
    }
    

    Wenn du willst könntest du dann auch eine Template-Funktion machen die einfach an die Overloads weiterleitet:

    class foo
    {
    private:
        void bar_impl(boost::type<baz>);
        void bar_impl(boost::type<qux>);
    public:
        template <class T>
        void bar()
        {
            return bar_impl(boost::type<T>());
        }
    };
    
    void test()
    {
        foo f;
        f.bar<baz>();
        f.bar<qux>();
    }
    

    Letzte Möglichkeit die ich anzubieten habe, und IMO die beste:

    template<class N> class A
    {
    public:
        vector<N*> nodes() const
        {
            return nodes_impl();
        }
    
    private:
        virtual vector<N*> nodes_impl() const = 0;
    };
    
    template<class N, class ON> class B : public virtual A<N>
    {
    public:
        vector<ON*> nodes() const // neue nodes() funktion, verdeckt A::nodes()
        {
            // ...
        }
    
    private:
        virtual vector<N*> nodes_impl() const // override von A::nodes_impl, implementiert A::nodes()
        {
            // ...
        }
    };
    


  • hustbaer schrieb:

    Member-Funktionen kann man soweit ich weiss nicht spezialisieren.

    Das stimmt. Bei Member-Funktions Templates geht das aber z.B. (sofern nicht partiell).

    Interessant ist aber, dass Visual Studio 2010 das hier ohne Probleme (sogar bei ausgeschaltener Spracherweiterung! 😮 ) kompiliert und ausführt:

    #include <iostream>
    
    template<class N, class ON>
    class A
    {
    public:
    
    	template<class T>
    	void nodes();
    
    	template<>
    	void nodes<N>()
    	{
    		std::cout << "N\n";
    	}
    
    	template<>
    	void nodes<ON>()
    	{
    		std::cout << "ON\n";
    	}
    
    private:
    };
    
    int main()
    {
    	A<int,float> a;
    	a.nodes<int>(); // N
    	a.nodes<float>(); // ON
    }
    

    Comeau Online bringt aber das hier:

    Comeau C/C++ 4.3.10.1 (Oct 6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
    Copyright 1988-2008 Comeau Computing. All rights reserved.
    MODE:non-strict warnings C++ C++0x_extensions

    "ComeauTest.c", line 11: error: explicit specialization is not allowed in the
    current scope
    template<>
    ^

    "ComeauTest.c", line 17: error: explicit specialization is not allowed in the
    current scope
    template<>
    ^

    2 errors detected in the compilation of "ComeauTest.c".



  • drakon schrieb:

    hustbaer schrieb:

    Member-Funktionen kann man soweit ich weiss nicht spezialisieren.

    Das stimmt. Bei Member-Funktions Templates geht das aber z.B. (sofern nicht partiell).

    Uff, jetzt bin ich selbst verwirrt.
    Sowohl MSVC fressen Spezialisierung von
    a) einer Non-Template-Member-Funktion eines Klassen-Templates
    b) eines Member-Funktions-Templates einer Non-Template-Klasse

    MSVC frisst dann weiterhin auch die Spezialisierung eines Member-Funktions-Templates eines Klassen-Templates, und zwar mit folgener Syntax:

    template <class K>
    class foo
    {
    public:
    	template <class F>
    	void test();
    };
    
    template <class K> // erst die klasse
    template <class F> // dann die funktion -> logisch soweit
    inline void foo<K>::test()
    {
    }
    
    template <> // funktion muss als erstes kommen
    template <class K> // dann die klasse, sonst frisst es MSVC nicht -> *verwirr*
    inline void foo<K>::test<int>()
    {
    }
    

    Comeau frisst es gar nicht.

    ----

    Aber ganz egal wie das jetzt mit den offiziellen Regeln ist, ich würde es auf jeden Fall nicht mit Spezialisierung machen, sondern mit der "nodes_impl" Variante.



  • hustbaer schrieb:

    Comeau frisst es gar nicht.

    Mich verwirrt am meisten, dass VC das schluckt, auch wenn man die Spracherweiterungen abschaltet. Ohne die sollte er sich doch exakt an den Standard halten.. :S

    ----

    Aber ganz egal wie das jetzt mit den offiziellen Regeln ist, ich würde es auf jeden Fall nicht mit Spezialisierung machen, sondern mit der "nodes_impl" Variante.

    Hmm. Ich sehe ehrlich gesagt den Grund nicht ganz, warum man das über überhaupt über templates steuern soll.. Lediglich dass man keinen Namen nehmen muss finde ich das ein wenig übertrieben.



  • drakon schrieb:

    hustbaer schrieb:

    Comeau frisst es gar nicht.

    Mich verwirrt am meisten, dass VC das schluckt, auch wenn man die Spracherweiterungen abschaltet. Ohne die sollte er sich doch exakt an den Standard halten.. :S

    DAS verwirrt mich nicht, MSVC hat einfach noch ein paar Bugs was Templates angeht 🙂

    ----

    Aber ganz egal wie das jetzt mit den offiziellen Regeln ist, ich würde es auf jeden Fall nicht mit Spezialisierung machen, sondern mit der "nodes_impl" Variante.

    Hmm. Ich sehe ehrlich gesagt den Grund nicht ganz, warum man das über überhaupt über templates steuern soll.. Lediglich dass man keinen Namen nehmen muss finde ich das ein wenig übertrieben.

    Äh. Die "nodes_impl" Variante von mir verwendet ja auch keine Templates.
    😕



  • Bugs und es funktioniert? 😛

    Das wegen den templates war eigentlich auf den Ursprungs post bezogen.



  • drakon schrieb:

    Bugs und es funktioniert? 😛

    Na klar.
    Bei VC6 konnte (bzw. musste) man typename weglassen und es hat funktioniert 🙂



  • drakon schrieb:

    template<class N, class ON>
    class A
    {
    public:
    
    	template<class T>
    	void nodes();
    
    	template<>
    	void nodes<N>()
    	{
    		std::cout << "N\n";
    	}
    
    	template<>
    	void nodes<ON>()
    	{
    		std::cout << "ON\n";
    	}
    
    private:
    };
    
    int main()
    {
    	A<int,float> a;
    	a.nodes<int>(); // N
    	a.nodes<float>(); // ON
    }
    

    Das gefällt mir so sehr gut. Nur will g++ das nicht kompilieren (habe -std=c++0x als Kompileroption gesetzt). Kann das g++ (bisher) überhaupt nicht? Falls dem so ist: Gibt es einen anderen Open-Source-Kompiler, der das kann?

    drakon schrieb:

    Hmm. Ich sehe ehrlich gesagt den Grund nicht ganz, warum man das über überhaupt über templates steuern soll.. Lediglich dass man keinen Namen nehmen muss finde ich das ein wenig übertrieben.

    Ich vermute, dass kann man so auch nicht sehen (ohne weitere Teile des Programms zu kennen). Bei mir ist nur der Eindruck entstanden, dass so wie es bisher ist, die Lesbarkeit meines Codes spürbar leidet.

    hustbaer schrieb:

    Letzte Möglichkeit die ich anzubieten habe, und IMO die beste:

    template<class N> class A
    {
    public:
        vector<N*> nodes() const
        {
            return nodes_impl();
        }
    
    private:
        virtual vector<N*> nodes_impl() const = 0;
    };
    
    template<class N, class ON> class B : public virtual A<N>
    {
    public:
        vector<ON*> nodes() const // neue nodes() funktion, verdeckt A::nodes()
        {
            // ...
        }
    
    private:
        virtual vector<N*> nodes_impl() const // override von A::nodes_impl, implementiert A::nodes()
        {
            // ...
        }
    };
    

    Ich habe wohl noch eine Nebenbedingung vergessen: Wenn ich ein Objekt b der Klasse B habe, würde ich gerne beide nodes()-Funktionen auf b anwenden können. Das geht bei der Lösung doch nur, indem ich b zu A caste?



  • ingobulla schrieb:

    Nur will g++ das nicht kompilieren (habe -std=c++0x als Kompileroption gesetzt). Kann das g++ (bisher) überhaupt nicht? Falls dem so ist: Gibt es einen anderen Open-Source-Kompiler, der das kann?

    Wie wärs wenn du uns die Infos lieferst, warum g++ das nicht compilieren will? Dann kann man dir vielleicht sogar helfen, g++ dazu zu bringen es doch zu compilieren...



  • pumuckl schrieb:

    Wie wärs wenn du uns die Infos lieferst, warum g++ das nicht compilieren will? Dann kann man dir vielleicht sogar helfen, g++ dazu zu bringen es doch zu compilieren...

    Danke für den freundlichen Hinweis:

    ../src/scratch.h:9: error: explicit specialization in non-namespace scope ‘
        class A<N, ON>’
    ../src/scratch.h:10: error: template-id ‘nodes<N>’ in declaration of
        primary template
    ../src/scratch.h:15: error: explicit specialization in non-namespace scope ‘
        class A<N, ON>’
    ../src/scratch.h:16: error: template-id ‘nodes<ON>’ in declaration of
        primary template
    ../src/scratch.h:16: error: ‘void A<N, ON>::nodes()’ cannot be overloaded
    ../src/scratch.h:10: error: with ‘void A<N, ON>::nodes()’
    


  • ingobulla schrieb:

    ../src/scratch.h:9: error: explicit specialization in non-namespace scope
    

    Heißt übersetzt: "Du hast eine Templatespezialisierung (nämlich die von nodes<N>, Anm.d.Übers.) in einem Gültigkeitsbereich (Scope) definiert, der kein Namespace ist! (Nämlich im Scope von class A<N, ON>)"

    Andersrum ausgedrückt heißt das, du musst die Spezialsierung außerhalb der Klasse definieren.
    Das ist allerdings auch nicht möglich, da void A<N, ON>::node<T>() als Ganzes ein Funktionstemplate ist, das nicht partiell spezialsiert werden darf - sprich, Spezialisierungen sind nur über N, ON und T möglich, nicht aber nur über T.
    Um das Problem zu lösen, könntest du die Spezialisierung auf ein Functor-Template umleiten, die dann möglich wäre.



  • drakon schrieb:

    Mich verwirrt am meisten, dass VC das schluckt, auch wenn man die Spracherweiterungen abschaltet. Ohne die sollte er sich doch exakt an den Standard halten.. :S

    Gibt es in MSVC++ wirklich so viele Erweiterungen auf Sprachebene (nicht CLI)? Mir fallen da z.B. Referenzen auf temporäre RValues ein, aber da gibts auch immer schön Warnungen.



  • ingobulla schrieb:

    Ich habe wohl noch eine Nebenbedingung vergessen: Wenn ich ein Objekt b der Klasse B habe, würde ich gerne beide nodes()-Funktionen auf b anwenden können. Das geht bei der Lösung doch nur, indem ich b zu A caste?

    Nö, du kannst direkt ptr->A::nodes() schreiben.



  • Nexus schrieb:

    drakon schrieb:

    Mich verwirrt am meisten, dass VC das schluckt, auch wenn man die Spracherweiterungen abschaltet. Ohne die sollte er sich doch exakt an den Standard halten.. :S

    Gibt es in MSVC++ wirklich so viele Erweiterungen auf Sprachebene (nicht CLI)? Mir fallen da z.B. Referenzen auf temporäre RValues ein, aber da gibts auch immer schön Warnungen.

    Naja da gibt's schon ein paar:

    http://msdn.microsoft.com/en-us/library/34h23df8(v=VS.80).aspx



  • Danke für den Link. Mal schauen, ob ich nicht aus Versehen ein paar davon benutze... 🙂



  • Nexus schrieb:

    drakon schrieb:

    Mich verwirrt am meisten, dass VC das schluckt, auch wenn man die Spracherweiterungen abschaltet. Ohne die sollte er sich doch exakt an den Standard halten.. :S

    Gibt es in MSVC++ wirklich so viele Erweiterungen auf Sprachebene (nicht CLI)? Mir fallen da z.B. Referenzen auf temporäre RValues ein, aber da gibts auch immer schön Warnungen.

    Ich weiss nicht in wie fern das dokumentiertes Verhalten ist, dass Sachen gehen, die nicht im Standard vorgesehen sind, aber ich bezweifle stark, dass das irgendwelche Bugs sind. Daher sind das imo Spracherweiterungen, da ja offensichtlich mehr akzeptiert wird.
    Ich war bis jetzt der Auffassung, dass VC beim Ausschalten der Spracherweiterungen sich wirklich so exakt an den Standard hält, wie möglich, auch wenn das bedeutet, dass gewisse Konstrukte (obwohl möglich) nicht gehen.



  • drakon, glaub mir, das sind z.T. Bugs:

    http://msdn.microsoft.com/en-us/library/x84h5b78.aspx

    Der "dependent names" Bug kann z.B. dazu führen dass Code geht, der nicht gehen sollte.
    Der Grossteil sind allerdings wirklich Bugs, wo höchstens Dinge nicht gehen, die eigentlich gehen sollten.


Log in to reply