Mehrdeutiger Operator



  • Hi, ich habe eine Double-Klasse geschrieben die einen

    operator double() const;
    

    Operator besitzt der diese in einen normalen double umwandeln kann. Nun habe ich eine weitere Klasse Thickness geschrieben die vier Double Member hat. Zudem sind für die Klasse Thickness die Operatoren

    bool operator==(const Thickness& a, const Thickness& b);
    bool operator!=(const Thickness& a, const Thickness& b);
    

    definiert.
    Wenn ich nun aber einen Double mit einem anderen Double vergleichen möchte
    z.B.

    Double a, b;
    a == a
    b != b
    

    Dann bekomme ich die Fehlermeldung C2088 und C2593

    IntelliSense: Mehr als ein "!="-Operator stimmt mit diesen Operanden überein:
    Integrierter "arithmetic != arithmetic"-Operator

    kann dies aber nicht nachvollziehen. Jemand eine Idee bzw. Lösung für dieses Problem.


  • Mod

    Wie Code posten

    Klassendefinition?
    Funktionsdeklarationen?
    Exakte Fehlermeldung einschließlich aller Hinweise dazu?



  • Ist dir schon aufgefallen, dass du a mit a vergleichst und b mit b?
    Des weiteren ist dir sicherlich bewusst, dass sowohl deine beiden Testdoubles (a, b) als auch die Funktionsparameter der Operatorüberladung dieselben Namen haben.
    Ansonsten schließ ich mich camper an. Vllt. liegt der Fehler in der double-Klasse?



  • Also die Probleme konnte ich jetzt damit beseitigen, dass ich zusätzlich zu

    bool operator==(const Double& a, const Double& b);
    

    noch

    bool operator==(double a, const Double& b);
    

    und

    bool operator==(const Double& a, double b);
    

    implementiert habe. Allerdings meckert er jetzt wieder an anderer Stelle, z.B.
    wenn folgendes geprüft wird

    if (value == 0)
    

    da er "0" als int interpretiert. D.h. wenn ich das richtig sehe muüsste ich jetzt noch zusätzlich für alle anderen Standard-Typen wie int, short, long long, usw. entsprechende Operatoren anbieten. Ganzschön aufwendig das ganze ;).



  • Und was wäre von dieser Implementierung der Operatoren zu halten. Ich traue es mich kaum den Code zu posten ;).

    bool operator!=(const Double& value1, bool value2)
    {
    	return (static_cast<double>(value1) != value2);
    }
    
    bool  operator!=(const Double& value1, double value2)
    {
    	return (static_cast<double>(value1) != value2);
    }
    
    bool operator!=(const Double& value1, float value2)
    {
    	return (static_cast<double>(value1) != value2);
    }
    
    usw.
    


  • Ohne das gutheißen zu wollen und ohne genau zu wissen, wo Du hinwillst, kann man das aber auch mit Templates lösen:

    template<typename T>
    bool operator==(const Double& d, const T& t)
    {
        return static_cast<double>(d) == t;
    }
    

    Und wie gesagt, mehr Code wär von Not.



  • Dein Problem wird dadurch ausgelöst, dass deine Doubleklasse einen Implizieten Konstruktor anbietet, der sie in eine normale Double umwandelt. Vermutlich hast auch einen Konstruktor, der eine double annimt. Da ist das Problem. Die impliziete Umwandung von Double in double muss weg! Mach eine Funktion draus!



  • Die erinnerung schrieb:

    Dein Problem wird dadurch ausgelöst, dass deine Doubleklasse einen Implizieten Konstruktor anbietet, der sie in eine normale Double umwandelt. Vermutlich hast auch einen Konstruktor, der eine double annimt. Da ist das Problem. Die impliziete Umwandung von Double in double muss weg! Mach eine Funktion draus!

    Das ist ein guter Tipp, habe jetzt testweise mal den implizieten Operator entfernt und eine Funktion angeboten die den normalen double zurückgibt. Diese Variante hat nur einen Haken. Ich kann jetzt z.B. keine Int32 mehr von einem Double subtrahieren, müsste also auch hier wieder sämtliche Operator-Kombinationen implementieren. Zudem hätte ich überall im Code diese GetValue-Funktion die den normalen double zurückgibt.

    Eisflamme schrieb:

    Ohne das gutheißen zu wollen und ohne genau zu wissen, wo Du hinwillst, kann man das aber auch mit Templates lösen:

    template<typename T>
    bool operator==(const Double& d, const T& t)
    {
        return static_cast<double>(d) == t;
    }
    

    Das wäre schonmal eine Erleichterung.


  • Mod

    Hat jemand eine Glaskugel übrig?



  • Das einfachste wäre ich könnte auf diese ganzen Operatoren verzichten und belasse den "implizieten" Operator. Allerdings habe ich da ja diese seltsame Fehlermeldung. Mir ist es sogar gelungen ein Minimalbeispiel zu stricken:

    bool operator==(const B& a1, const B& a2);
    bool operator!=(const B& a1, const B& a2);
    
    class A
    {
    public:
    	operator double() const;
    private:
    	double value;
    };
    
    A::operator double() const
    {
    	return value;
    }
    
    class B
    {
    public:
    	B(A a);	//Das ist der Schlawiener
    
    	void Test();
    private:
    	A a1;
    	A a2;
    };
    
    B::B(A a)
    {
    }
    
    void B::Test()
    {
    	if (a1 == a2)    // HIER KOMMT DER FEHLER
    	{
    	}
    }
    
    bool operator==(const B& a1, const B& a2)
    {
    	return true;
    }
    
    bool operator!=(const B& a1, const B& a2)
    {
    	return true;
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	return 0;
    }
    

    Warum wird hier ein mehrdeutiger Operator gefunden?


  • Mod

    MejrdeutigerOperator schrieb:

    Warum wird hier ein mehrdeutiger Operator gefunden?

    Weil die Konvertierung durch Konstruktor gleich gut/schlecht wie die mittels Konvertierungsoperator ist. Welche Variante willst du denn eigentlich?
    Den eingebauten Operator oder den für B? Oder etwa einen für A, den du vergessen hast zu implementieren.
    Du könntest auch den Konstruktor von B (oder auch den Konvertierunsgoperator in A mit C++11) explizit deklarieren. Ob das semantisch sinnvoll ist, lässt sich nat. bei Namne wie A und B kaum begründen.



  • Häh, ich vergleiche hier zwei Objekte der Klasse A das hat mit der Klasse B rein gar nichts zu tun. Warum der Compiler nun meint die A's in ein B umzuwandeln zu müssen ist mir ein Rätsel. Scheinbar sind Computer noch viel dümmer als ich angenommen hatte. Das einzig logische wäre die A's in einen double zu wandeln.
    @camper
    Nochmal hier gibt es keine Varianten. Klasse A und B hat rein gar nichts miteinander zu tun. Wozu gebe ich diesem bescheuerten Compiler im operator== dann überhaupt noch Datentypen an, wenn er sowieso alle Datentypen in betracht nimmt.



  • Um es nochmal so auszudrücken. Der Compiler merkt also, dass für die Klasse A gar kein operator== definiert wurde. Anstatt nun Klasse A dank des implementierten

    operator double() const;
    

    Operators in einen double zu wandeln, nimmt er sich stattdessen den nächst besten operator== den er finden kann, auch wenn dieser für ganz andere Datentypen definiert wurde. Unglaublich.



  • Ich kann ja noch nachvollziehen, dass der Compiler aufgrund des Konstruktors der Klasse B der einen double entgegen nimmt auch diesen "Umwandlungsweg" in Betracht nimmt, aber er müsste dann doch merken, dass ein A nunmal kein B ist und dafür auch keine Umwandlung vorhanden ist und diesen "Umwandlungsweg" dann wieder verwerfen.



  • 🙄

    void B::Test()
    {
        if (a1 == a2)    // HIER KOMMT DER FEHLER
        {
        }
    }
    

    Natürlich kommt da ein Fehler. Soll da nun

    1. a1 und a2 implizite nach B konvertiert und danach deine operator== Funktion aufgerufen werden?
    2. a1 und a2 impliziet nach double konvertiert und danach die built-in operator== Funktion aufgerufen werden?

    Diese Frage kannst nur dur beantworten, nicht ich nicht der Compiler und auch sonst niemand. Also sei so lieb und beantworte diese Frage dem Compiler. 😉

    Du kannst also entweder die implizite Konvertierung nach B verhindern, indem du den Konstruktor explicit machst. Oder aber du castest explizit nach double .


  • Mod

    MehrdeutigerOperator schrieb:

    Häh, ich vergleiche hier zwei Objekte der Klasse A das hat mit der Klasse B rein gar nichts zu tun. Warum der Compiler nun meint die A's in ein B umzuwandeln zu müssen ist mir ein Rätsel. Scheinbar sind Computer noch viel dümmer als ich angenommen hatte. Das einzig logische wäre die A's in einen double zu wandeln.
    @camper
    Nochmal hier gibt es keine Varianten. Klasse A und B hat rein gar nichts miteinander zu tun. Wozu gebe ich diesem bescheuerten Compiler im operator== dann überhaupt noch Datentypen an, wenn er sowieso alle Datentypen in betracht nimmt.

    class B
    {
    public:
    	B(A a);
    

    Hiermit wird ein Konvertierungskonstruktor deklariert. Somit kann an (fast) jeder Stelle, an der eigentlich ein B erwartet wird, ein A verwendet werden.
    Der Vergleichsoperator für B hat 2 B-Parameter, und kann dank dieses Konvertierungskonstruktors auch mit A-Argumenten aufgerufen werden.
    Gleichermassen kann der eingebaute Vergleichsoperator für double eigentlich nur mit double-Argumenten arbeiten. Dank des deklarierten double-Konvertierungsoperators kann dieser Vergleichsoperator aber auch mit A-Argumenten gefüttert werden.

    Beide Optionen sind aus Sicht des Compilers gleichwertig, weil sie beide den Aufruf einer Konvertierungsfunktion erfordern.

    Es gibt verschiedene Wege, diese Mehrdeutigkeit aufzulösen, allerdings keinen allgemingültigen, weil sie an anderer Stelle unterschiedliche Folgen haben. Konsequenterweise lässt sich mit Metasyntaktischen A und B kein guter Rat geben.
    Die einzige Regel, die meist beachtet werden kann, ist es , implizite Konvertierungen nur Bedacht einzusetzen (was nicht weiterhilft, wenn sie eben doch mal gebraucht werden).


  • Mod

    MehrdeutigerOperator schrieb:

    Ich kann ja noch nachvollziehen, dass der Compiler aufgrund des Konstruktors der Klasse B der einen double entgegen nimmt auch diesen "Umwandlungsweg" in Betracht nimmt, aber er müsste dann doch merken, dass ein A nunmal kein B ist und dafür auch keine Umwandlung vorhanden ist und diesen "Umwandlungsweg" dann wieder verwerfen.

    B hat keinen Konstruktor für double, nur einen für A.



  • [gg]

    camper schrieb:

    ]B hat keinen Konstruktor für double, nur einen für A.

    Da hast du du natürlich recht.
    Wie würdet ihr denn eine solche Wrapper-Klasse, um einen Standard-Datenyp gestalten?



  • MehrdeutigerOperator schrieb:

    [gg]

    camper schrieb:

    ]B hat keinen Konstruktor für double, nur einen für A.

    Da hast du du natürlich recht.
    Wie würdet ihr denn eine solche Wrapper-Klasse, um einen Standard-Datenyp gestalten?

    Gar nicht?

    Ohne jetzt den Thread zu erforschen, wofür brauchste das?

    Ich würde einfach einen Konvertierungsoperator für eine Referenz und eine Kopie bereitstellen, und ein Internes Objekt halten.



  • Möglicherweise sowas:

    template<typename Type>
    struct ScalarWrapper
    {
            static_assert(std::is_arithmetic<Type>::value, "Has to have scalar type!");
    
            ScalarWrapper(Type const t) noexcept:
                    mInst(t) {}
    
            constexpr ScalarWrapper() noexcept = default;
    
            operator Type&() noexcept {return mInst;}
            operator Type() const noexcept {return mInst;}
    
    private:
    
            Type mInst;
    };
    

Anmelden zum Antworten