Variadic Templates - Klassen-Parameter-Packs und Methoden

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
RustySpoon
Establishment
Beiträge: 298
Registriert: 17.03.2009, 13:59
Wohnort: Dresden

Variadic Templates - Klassen-Parameter-Packs und Methoden

Beitrag von RustySpoon »

Hallo allerseits!

Ich hab eine kleine Frage für die C++11-Cracks unter euch. Ich bin mit einigen neuen Sprachfeatures leider noch nicht so ganz firm und bevor ich jetzt ewig rumbastel... Ich möchte im Wesentlichen das Parameter-Pack einer Klasse innerhalb einer ihrer Methode abrollen.

Etwas Beispiel-Code:

Code: Alles auswählen

struct base1
{
    void base_test() { std::cout << "base 1" << std::endl; }
};

struct base2
{
    void base_test() { std::cout << "base 2" << std::endl; }
};

struct base3
{
    void base_test() { std::cout << "base 3" << std::endl; }
};


template<typename ...Args>
struct derived : public Args...
{
    void test() {}

    template<typename T, typename ...TestArgs>
    void test()
    {
        T::base_test();

        test<TestArgs...>();
    }

    void my_test()
    {
        test<Args...>();
    }
};
Wirft bei mir (GCC 4.6) folgende Fehlermeldung:
ssa/main.cpp: In member function 'void derived<Args>::test() [with T = base3, TestArgs = {}, Args = {base1, base2, base3}]':
ssa/main.cpp:74:9: recursively instantiated from 'void derived<Args>::test() [with T = base2, TestArgs = {base3}, Args = {base1, base2, base3}]'
ssa/main.cpp:74:9: instantiated from 'void derived<Args>::test() [with T = base1, TestArgs = {base2, base3}, Args = {base1, base2, base3}]'
ssa/main.cpp:79:9: instantiated from 'void derived<Args>::my_test() [with Args = {base1, base2, base3}]'
ssa/main.cpp:97:16: instantiated from here
ssa/main.cpp:74:9: error: no matching function for call to 'derived<base1, base2, base3>::test()'
ssa/main.cpp:74:9: note: candidate is:
ssa/main.cpp:70:19: note: template<class T, class ... TestArgs> void derived::test() [with T = T, TestArgs = {TestArgs ...}, Args = {base1, base2, base3}]
Scheint so, als wenn der Base Case nicht akzeptiert wird. Ideen?
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: Variadic Templates - Klassen-Parameter-Packs und Methode

Beitrag von BeRsErKeR »

Probiers mal so:

Code: Alles auswählen

template<typename ...Args>
struct derived : public Args...
{
    template<typename T>
    void test() 
    {
        T::base_test();
    }

    template<typename T, typename ...TestArgs>
    void test()
    {
        T::base_test();

        test<TestArgs...>();
    }

    void my_test()
    {
        test<Args...>();
    }
};
Ich gehe mal davon aus, dass ein Aufruf wie test<TestArgs...>() auch mit leerer Paramterliste keine Nicht-Template-Methode aufrufen kann. Darum ist die letzte Methode in der Aufrufkette ebenfalls eine Template-Methode die nur noch einen Parameter erhält und nicht mehr rekursiv Methoden aufruft.

Prinzipiell sollte deine Variante natürlich auch funktionieren, jedenfalls laut Standard, allerdings müssen die Implementierungen von Variadic Templates ja nicht unbedingt zu 100% standardkonform sein. Bislang war es wohl selten so, dass sich ein Compiler immer zu 100% an den Standard hielt oder alles zu 100% korrekt implementiert hatte (gerade was Templates angeht).
Ohne Input kein Output.
Benutzeravatar
RustySpoon
Establishment
Beiträge: 298
Registriert: 17.03.2009, 13:59
Wohnort: Dresden

Re: Variadic Templates - Klassen-Parameter-Packs und Methode

Beitrag von RustySpoon »

Danke für die Antwort.

In diesem 'printf'-Beispiel http://en.wikipedia.org/wiki/Variadic_template wird das analog gemacht.

Wenn ich statt dem 0-Argumente-Fall, den 1-Argument-Fall abfange, hab ich das Problem, dass der Aufruf nicht mehr eindeutig ist.
ssa/main.cpp:76:9: error: call of overloaded 'test()' is ambiguous
ssa/main.cpp:76:9: note: candidates are:
ssa/main.cpp:68:10: note: void derived<Args>::test() [with T = base3, Args = {base1, base2, base3}]
ssa/main.cpp:72:10: note: void derived<Args>::test() [with T = base3, TestArgs = {}, Args = {base1, base2, base3}]
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: Variadic Templates - Klassen-Parameter-Packs und Methode

Beitrag von BeRsErKeR »

Ja ich kenne das Beispiel aber wie schon gesagt kann es sein, dass der gcc das noch nicht zu 100% umsetzt. Ich weiß auch gerade nicht wie der Standard dazu steht. Wikipedia darf man halt auch nicht immer glauben.

Zum Problem mit Eindeutigkeit. Als Workaround könntest du der zweiten Version zwei feste Templateparameter + Args... geben und beim Aufruf dann das zweite mit übergeben. Dann sollte es vorerst klappen.
Ohne Input kein Output.
Benutzeravatar
RustySpoon
Establishment
Beiträge: 298
Registriert: 17.03.2009, 13:59
Wohnort: Dresden

Re: Variadic Templates - Klassen-Parameter-Packs und Methode

Beitrag von RustySpoon »

Das Wiki-Beispiel funktioniert bei mir schon, allerdings vermute ich, der Compiler kann die Funktionen da anhand der Parameterliste unterscheiden oder so, naja.
BeRsErKeR hat geschrieben:Zum Problem mit Eindeutigkeit. Als Workaround könntest du der zweiten Version zwei feste Templateparameter + Args... geben und beim Aufruf dann das zweite mit übergeben. Dann sollte es vorerst klappen.
So klappt's erstmal. Ist aber irgendwie unschön.

Code: Alles auswählen

template<typename ...Args>
struct derived : public Args...
{
    template<typename T>
    void test()
    {
        T::base_test();
    }

    template<typename T1, typename T2, typename ...TestArgs>
    void test()
    {
        T1::base_test();
        T2::base_test();

        test<TestArgs...>();
    }

    void my_test()
    {
        test<Args...>();
    }
};
Ich installier mal den 4.6.2er GCC, mal schauen was passiert.
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: Variadic Templates - Klassen-Parameter-Packs und Methode

Beitrag von BeRsErKeR »

Hab mir das Beispiel aus Wikipedia nochmal angeguckt. Das ist ja schon etwas anderes. Du spezifizierst beim Aufruf von test explizit durch die spitzen Klammern, dass es sich um eine Template-Methode handelt. Das heißt am Ende rufst du quasi test<LEER>() auf und das ist nicht unbedingt das Gleiche wie test(). Im Wiki-Beispiel wird die Methode anhand der Parameter unterschieden. Im Base Case ist das halt genau printf(const char *). Das Problem mit der Eindeutigkeit wird dort auch umgangen, indem die Template-Version mindestens 2 Parameter aufweist (also ähnlich dem jetzigen Workaround, nur dass es da halt auch in der Form Sinn macht).

Ich glaube daher dass dein Vorhaben nicht wirklich standardkonform ist und du vielleicht wirklich nur mit dem unschönen Workaround zum Ziel kommst. Ich hatte den Code übrigens noch ein bischen anders im Kopf gehabt, aber deine Variante geht auch. Ich hätte T2 mit vor den test-Aufruf gepackt: test<T2, TestArgs...>(); und nur T1::base_test() aufgerufen. Aber ist ja auch egal.
Ohne Input kein Output.
Antworten