VC++7 -> ambiguous call to overloaded function - bei templates



  • Hallo.

    warum meint der VC++7, dass es hier nicht eindeutig ist, welche funktion er aufrufen soll?

    #include<iostream>
    using namespace std;
    
    template<class T>
    class Holder
    {
    private:
        T t;
    public:
        Holder(T t)
        : t(t)
        {
        }
    
        int foo() const
        {
            return t.foo();
        }
    };
    
    class X
    {
    private:
        int val;
    public:
        X(int val) : val(val)
        {
        }
    
        int foo() const
        {
            return val;
        }
    };
    
    class Test
    {
    public:
        template<class T>
        void foo(Holder<T> t)
        {
            cout<<"a: "<<t.foo()<<'\n';
        }
    
        template<class T>
        void foo(T t)
        {
            cout<<"b: "<<t.foo()<<'\n';
        }
    };
    
    int main()
    {
        Test t;
        t.foo(X(3)); //alles OK
        t.foo(Holder<X>(X(7))); //ambiguous
    }
    

    t.foo(X(3));
    ist in ordnung, es passt ja nur die b-funktion.

    aber theoretisch (und der gcc gibt mir recht) passt doch beo
    t.foo(Holder<X>(X(7)));
    die a-funktion viel besser...

    wie kann ich ihm das beibringen, ohne das interface zu aendern?



  • warum meint der VC++7, dass es hier nicht eindeutig ist, welche funktion er aufrufen soll?

    Weil weder der VC 6 noch der VC 7 "partial ordering of function templates" beherrschen.
    <proll>
    Ich habe in einem ziemlich bekannten C++ Buch mal ein Bonus-Kapitel verfasst, in dem unter anderem ein kanonischer Workaround beschrieben wird.
    </proll>
    Natürlich ist der nicht von mir. Du findest ihn sehr gut erklärt im VC7-Loki-Port.

    Eine Lösung auf die schnelle:

    ...
    struct A {};
    struct B : public A {};
    
    class Test 
    { 
    private: 
        template<class T> 
        void fooimpl(Holder<T> t, B) 
        { 
            cout<<"a: "<<t.foo()<<'\n'; 
        } 
    
        template<class T> 
        void fooimpl(T t, A) 
        { 
            cout<<"b: "<<t.foo()<<'\n'; 
        } 
    public:
    	template<class T> 
        void foo(T t) 
        { 
            // forwarding zur eigentlichen Funktion
    		// Für T = Holder<T> passt die erste fooimpl-Variante besser,
    		// Den ersten Parameter kann der VC nicht unterscheiden.
    		// T passt genausogut wie Holder<T>
    		// Der zweite Parameter hingen passt für dier erste Variante besser,
    		// da B ein "perfect match" ist. A würde eine Derived-nach-Base-Kovertierung
    		// voraussetzen.
    		fooimpl(t, B());
        } 
    };
    

    Falls das Kopieren von T teuer ist, solltest du die Parameter der impl-Funktionen noch zu Referenz-auf-const machen.



  • Auch wenns dem Problem selbst nicht hilft:

    Der VC7 Compiliert das.

    Ausgabe:

    b: 3
    a: 7



  • @Hume:
    danke!

    dein kapitel in Modern C++ Design habe ich nur teilweise gelesen (forumtreffen) - denn ich depp habe mir natuerlich die englische fassung gekauft...

    es funktioniert einwandfrei, aber waerst du trotzdem so freundlich mir zu erklaeren, warum es funktioniert? (ich komm einfach nicht dahinter :()

    @Knuddlbaer:
    der VC++7**.1** kann das vielleicht. aber der VC++7**.0** kann es nicht - zumindest meiner nicht.



  • der VC behandelt T und Holder<T> anscheinend gleichwertig. bei

    foo (int(0));
    

    ist klar, dass int kein holder ist, also nimmt er foo(T t).
    bei

    foo (Holder<int>(0))
    

    erzeugt er

    //aus foo(T t)
    foo (Holder<int>(0)); //mit T = Holder<int>
    //und aus foo(Holder<T> T)
    foo (Holder<int>(0)); //mit T = int
    

    und wie es aussieht, behandelt er die zwei gleich, was zu ambigous führt.

    mit dem zusatzparameter sieht es aber so aus:

    //im fall von foo(int(0)) ruft er auf
    foo (T t, A); //da das B sich nach A konvertieren lässt
    //foo(Holder) steht nicht zur debatte, da int kein holder ist
    
    //im fall von foo(Holder<int>(0)) hat er die wahl zwischen
    foo (T t, A); //mit T = Holder<int>
    //und
    foo (Holder<T> t, B); //mit T = int
    

    und weil du als zweites Argument ein B übergibst, bevorzugt der Compiler foo(Holder<T> t, B), da das andere foo eine Konvertierung von B nach A benötigt hätte.
    Also im einen Fall konvertiert er das B nach A, weil er keine andere Möglichkeit hat, im zweiten Fall muss er nicht nach A konvertieren und bleibt bei foo mit Holder

    ich hoffe, ich konnte aushelfen 😉



  • *patsch*

    das klingt logisch.

    danke! 👍



  • @Knuddlbaer:
    der VC++7.1 kann das vielleicht. aber der VC++7.0 kann es nicht - zumindest meiner nicht.

    Jupp ich weiß. Hatte ja auch geschrieben das es dem Problem nicht hilft, wollt es ja nur kurz erwähnt haben das der 7.1 das schafft. Rein Informativ ohne Dir bei Deinem Problem direckt geholfen zu haben.


Anmelden zum Antworten