conversion operator erzeugt zuviele Interpretationen von operator ==


  • Mod

    Welcher compiler soll hier Schwierigkeiten haben?



  • camper schrieb:

    Welcher compiler soll hier Schwierigkeiten haben?

    MS Visual C++, von der Express Edition. Den probiere ich gerade neu aus.
    Kenn mich also mit Switches und Einstellungen nicht besonders aus. Generell habe ich den Eindruck er ist mehr restriktiv und mehr auf Sicherheit ausgerichtet, als der Compiler von Borland.



  • Kann ich nicht nachvollziehen, bitte kompilierbares Beispiel!

    DerVollstreckerDer Vollstrecker



  • Der Vollstrecker schrieb:

    Kann ich nicht nachvollziehen, bitte kompilierbares Beispiel!

    DerVollstreckerDer Vollstrecker

    Kompilierbares Beispiel. Kann das leider heute nicht mehr machen. (Weil ich dafür die Settings für eine Konsolenanwendung erst neu einstellen müsste.) Kann ich morgen machen.

    Aber ich finde das Problem ist relativ klar aus der Error Meldung: der Konversions Operator verwandelt mein SString in ein char*. Und damit sind 2 Operatoren gleichzeitig prinzipiell auf die == Situation anwendbar.

    Also der Compilierbare Code würde so aussehen:

    int _tmain(int argc, _TCHAR* argv[])
    {
    
    	char animal[] = "Fuchs";
    	char* p       = animal;
    	SString tier  = "Ente"
    
        if(tier == 	p)
    	   return 1;
    	return 0;
    }
    

    Nur ich habe noch nicht testen können, dass dies mit derselben Fehlermeldung NICHT compiliert.



  • Deine Settings gehen mir ehrlich gesagt am hcsrA vorbei. Folgendes funktioniert ohne Probleme:

    struct Foo {
        int bar;
        Foo(int val) : bar(val) {}
        operator int() const { 
            return bar; 
        }
        friend bool operator==(Foo const& foo, int val) { 
            return foo.bar == val; 
        }
    };
    
    int main() {
        return Foo(1) == 1;
    }
    

    DerVollstreckerDer Vollstrecker



  • @hgf: Habe bei mir das selbe Problem (MSVC++.NET 2003). Musst halt auch noch den == Operator mit char* überladen. Nervt gewaltig, aber sind halt die dollen eingebauten Operatoren 😡


  • Mod

    Der Microsoft Compiler hat natürlich recht, und es ist nur billig, dass dir ein solch schlechtes Design um die Ohren gehauen wird. Grundsätzlich sind Konvertierungsoperatoren und -konstruktoren mit großer Vorsicht zu genießen. Eine Funktion ist besser als eine andere für Zwecke der Überladungsauflösung, wenn die notwendige Konvertierungssequenz für keinen der Parameter dieser Funktion schlechter (im Rang) ist, als diejenige der anderen Funktion für den selben Parameter und wenigstens eine dieser Sequenzen besser ist, als die entsprechende Sequenz für die andere Funktion (Und dann noch ein paar Regeln für Templatefunktionen und eine spezielle Regel für Konvertierungsoperatoren, die hier keine Rolle spielen). Hier erfolgt also eine partielle Ordnung nicht zufällig analog der partiellen Ordnung von Spezialisierungen von Klassentemplates.

    -Die erste Operatorüberladung 'int SString::operator ==(const char )'
    erfordert keine (oder man könnte sie auch als identische K.-sequenz bezeichenn) Konvertierungssequenz für den ersten Parameter, aber eine Qualifikationskonvertierung (von char
    auf const char*) für den 2.Parameter
    -Die zweite Operatorüberladung operator==(char *, char 😉
    dagegen erfordert die zweitschlechteste Form einer Konvertierungssequenz, nähmlich eine, die einen Nutzerdefinierten operator benötigt (schlechter sind Ellipsen), dagegen keine für den zweiten Parameter.

    Die erste Überladung ist also besser für den ersten Operanden aber schlechter für den zweiten Operanden. Es ist also nur logisch und folgerichtig, wenn der Compiler hier eine Entscheidung verweigert. Im Übrigen ist das - wie erwähnt - nur Folge eines schlechten Designs und nicht ein Fehler der Sprachkonzeption.



  • @ Vollstrecker:

    Habe das Beispiel jetzt mit einer künstlichen SString Klasse gemacht:

    struct SString
     { char m_str[20];
       SString()   {;}  
       operator    char* ()    { return m_str; } 
       friend bool operator == (const SString&, const SString&) { return 1; }   
       friend bool operator == (const SString&, const char* )   { return 0; }
     };
    
    int main()
    {
    	char animal[] = "Fuchs";
    	char* p = animal;
    	SString tier;
    	SString tier2;
    	if(tier == p)              // hier selbe Fehlermeldung  wie zuvor
                return 1;               // aber siehe unten        
    	if(tier == tier2)          // ok
                return 1;
    }
    

    @Camper

    Weiss nicht, ob ich das mit dem Rang einer Konvertierungssequenz recht verstanden habe. Intuitiv finde ich, dass operator ==(const SString&, const char 😉 gewählt werden sollte, wenn die Argumente entsprechend sind. Und so macht es der Compiler ja auch, wenn kein Conversion Operator definiert ist.

    Aber Du hast mich darauf gebracht, auch die Umwandlung const - non-const als conversion zu berücksichtigen. Es stellt sich heraus, dass folgendes funktioniert (Veränderung zu oben ist die unterschiedliche Qualifikation von char* mit const):

    operator    const char* ()    { return m_str; } 
       friend bool operator == (const SString&, const SString&) { return 1; }   
       friend bool operator == (const SString&, const char* )   { return 0; }
    

    ODER

    operator    char* ()    { return m_str; } 
       friend bool operator == (const SString&, const SString&) { return 1; }   
       friend bool operator == (const SString&, char* )         { return 0; }
    

    also char* ist nun beide male gleichartig. Verstehen tu ich es noch nicht, aber zumindest kompiliert es jetzt.



  • Das Design is deshalb nich besser geworden. Das Template std::basic_string hat nicht umsonst keinen Konvertierungoperator sondern bietet hierzu die Methode c_str() an.

    grüße



  • Was soll an dem Design schlecht sein? Wieso soll ein Konvertierungsoperator schlechter sein als ein c_str()? Schließlich besteht die Klasse nur aus einem char*, warum also keinen Konvertierungsoperator dafür einbauen?
    Man kann die Klasse intuitiv benutzen und es ergibt sich kein Nachteil! Was soll an diesem Design schlecht sein 😕



  • Badestrand schrieb:

    Was soll an dem Design schlecht sein? Wieso soll ein Konvertierungsoperator schlechter sein als ein c_str()? Schließlich besteht die Klasse nur aus einem char*, warum also keinen Konvertierungsoperator dafür einbauen?
    Man kann die Klasse intuitiv benutzen und es ergibt sich kein Nachteil! Was soll an diesem Design schlecht sein 😕

    An c_str() ist besser das man SString Objekte nicht "ausversehen" in char* umwandeln kann...



  • Ja, aber was ist dann an einem const char* Operator so schlecht?



  • Das man evtl. fehlerhafte Übergaben an Funktionen wegen der impliziter Konvertierung nicht erkennt.


  • Mod

    Badestrand schrieb:

    Ja, aber was ist dann an einem const char* Operator so schlecht?

    Schlecht ist, dass er immer dort aufgerufen werden kann, wo die Sprache das vorsieht, und nicht nur an den Stellen, an die der Programmierer denkt, wenn er diesen Operator einführt. Ein Konvertierungsoperator, der in einen primitiven Typ konvertiert, macht alle Operationen, die für diesen primitiven Typ schon eingebaut sind, auch dem ursprünglichen Typ zugänglich. Das muss sehr schnell zu Mehrdeutigkeiten (im besten Falle) oder zu stillen Bedeutungsänderungen führen. Viele Konstrukte, die eigentlich falsch sind und diagnostiziert werden sollten, können so auf Umwegen doch noch legal werden.
    In deinem Beispiel sollte

    if(tier == p)
    

    natürlich den selbstdefinierten Vergleichsoperator benutzen. Tatsächlich funktioniert das aber auch dann noch, wenn du gar keinen Vergleichsoperator definierst. Die Bedeutung des Ausdrucks tier==p hängt also nicht bloß von den Dingen, die explizit dastehen (also den Operanden und deren Typen und dem Vergleichsoperator) ab, sondern noch von etwas, was gar nicht dasteht, nähmlich ob und, wenn ja, welche Konvertierungsoperatoren/-konstruktoren definiert wurden. Das macht so einen einfachen Ausdruck kompliziert und das ist per se schlecht. Wie soll das erst werden, wenn wir etwas haben, was an sich schon sehr komplex ist.



  • Hm, ich erkenne immer noch keinen Nachteil... Aber nicht, dass ihr mich falsch versteht, ich habe keine besonderen Argumente für einen Konv.Op., mch interessiert das halt nur weil ich selber solche Klassen hab...
    Bei einem char*-Op versteh ich das ja, dass da was falsch laufen kann, aber ein const char* darf man halt nicht verändern, also weiß ich nicht, was da in die Hose gehen sollte...
    Jetzt am Beispiel der String-Klasse: Die Klasse ist ja nur ein Wrapper für den Typ char*, da die Daten an sich aber nur von der Klasse selber verändert werden dürfen (OOP halt), kann man eben nur einen const char* bekommen.
    Wenn man einer Variable der Klasse an eine Funktion übergibt, die einen const char* erwartet, dann ist es doch gut so, die Funktion kann ja nix falsches mit anstellen.

    Habt ihr vielleicht ein (mehr oder weniger) konkretes Beispiel, wie es "gefährlich" werden könnte, bzw halt nicht im Sinne des Benutzers?



  • Gefährlich für den Benutzer kann es werden, wenn du beispielsweise den operator== vergisst. Bei einer "normalen" Klasse dürfte der Compiler sich beschweren, wenn du da zwei Objekte vergleichen willst, mit deinem Konversions-Operator bekommst du einfach Müll (char*-Vegleiche arbeiten auf den Adressen, für deine String-Klasse benötigst du vermutlich einen Inhalts-Vegleich). Und noch schlimmer wird das bei gemischten Objekten:

    class string
    {
    public:
      string(const char*);
      operator const char*() const;
      friend operator==(const string& l, const string& r);
    }
    
    string ob1;
    char ob2[20];
    if(ob1==ob2)...
    //soll ich ob1 per operator char* umwandeln und den eingebauten char* Vergleich durchführen?
    //oder soll ich ob2 per Ctor in einen string umwandeln und obigen op== verwenden?
    


  • class string
    {
    public:
      string(const char*);
      operator const char*() const;
      friend operator==(const string& l, const char* r);
    }
    
    string ob1;
    char ob2[20];
    
    if(ob1==ob2)...
    
    string ob3
    if (ob1==ob3) ...
    

    Wenn ich Compiler wäre, würde ich sowohl ob2 als auch ob3 in const char* umwandeln, dann müsste es ja funktionieren...
    Der Compiler müsste ja alle in Frage kommenden Vergleichsobjekte (also char* char[], const char*, const char[]) in const char* umwandeln können und damit den Vergleichsoperator aufrufen können.
    Würde auch funktionieren, wenn es nicht den eingebauten char* == char* Operator geben würde^^

    Oder hab ich da was falsch verstanden? Jedenfalls kommt es mir so vor, als würde es komplett ohne casten gehen..??.?



  • Das Problem ist das man sehr schnell Fehler macht, weil man die ganzen Möglichkeiten die sich, aufgrund impliziter Konvertierung, ergeben garnicht überblicken kann. Die Anzahl der Möglichkeiten ist bei konstanten Typen geringer, es existieren aber immer noch genug die dumme Fehlerquellen sind. Das schlimme ist die Fehlersuche, du wirst bei einem großen Projekt fluchen wenn du so einen Fehler suchen musst und Tage deines Lebens verschwendest diesen zu finden!

    grüße



  • Wenn ich Compiler wäre, würde ich sowohl ob2 als auch ob3 in const char* umwandeln, dann müsste es ja funktionieren...

    Ja, denke ich auch. (Allerdings betrachte ich den operator == (string&, string&) als den für die Klasse typischen Operator und den
    operator == (string&, char*) als sekundäre Anpassung an externe char*'s.)

    Würde auch funktionieren, wenn es nicht den eingebauten char* == char* Operator geben würde^^

    Oder hab ich da was falsch verstanden?

    Beim dem Satz oben bekomme ich schon den Eindruck, dass Du vielleicht etwas falsch verstanden hast. Der eingebaute operator == (char*, char*) ist ja das, was in den diskutierten Situationen immer vermieden werden muss. Ich weiss nicht ob Du string und char* zu sehr vermischt. Du hattest da einige nicht ganz richtigen Formulierungen wie:

    Schließlich besteht die Klasse nur aus einem char*, ...

    Für den string-vergleich spielt es in der Tat keine so grosse Rolle, dass die Klasse auch noch andere Elemente enthält.

    In meinen Beispielen habe ich statt des notwendigen Codes für den Vergleich immer nur return 0/1 geschrieben, nur um etwas kompilierbares zu haben. Ich hoffe, das hat niemanden verwirrt.



  • hgf schrieb:

    In meinen Beispielen habe ich statt des notwendigen Codes für den Vergleich immer nur return 0/1 geschrieben, nur um etwas kompilierbares zu haben. Ich hoffe, das hat niemanden verwirrt.

    Glaube nicht, dass du jemanden verwirrt hast 🙂

    hgf schrieb:

    Allerdings betrachte ich den operator == (string&, string&) als den für die Klasse typischen Operator [...]

    Im Prinzip ja, aber IMO bräuchte man als zweiten Parameter ja gar kein string&, wenn der sowieso in ein const char* gecastet wird (werden kann). Deckt man halt ein größeres Feld mit dem Operator ab 🤡

    hgf schrieb:

    Für den string-vergleich spielt es in der Tat keine so grosse Rolle, dass die Klasse auch noch andere Elemente enthält.

    Ich dachte immer, der Compiler benutzt die eingebauten Operatoren, wenn die Klasse nur aus einem Typ besteht...?

    hgf schrieb:

    Der eingebaute operator == (char*, char*) ist ja das, was in den diskutierten Situationen immer vermieden werden muss.

    Das wollte ich ja auch gar nicht! Mir ging es ja nur um den Operator const char*. Also auch operator== (const char*, const char*). Gilt da das auch noch mit dem Vermeiden? Wenn ja, warum?

    PS: Danke für die Antwort 👍


Anmelden zum Antworten