Rückgabewert überladen



  • Salut zusammen

    Mich hätte mal interessiert, warum es eigentlich nicht möglich ist, Rückgabewerte zu überladen? Gibts da ne logische Erklärung?

    Sollte doch für den Compiler kein Problem sein, bei allfälligen Unklarheiten, also beispielsweise wenn die Funktion ohne Rückgabewert benutzt wird, aber verschieden überladene Funktionen existieren, eine Fehlermeldung auszugeben a la "Funktion nicht eindeutig zuzuordnen"?

    Gruss



  • Gibt es, nur nicht (direkt) in C++. Nennt sich double dispatch. Kann man aber nachbauen 🙂 http://stackoverflow.com/questions/429849/double-dispatch-multimethods-in-c



  • Double Dispatch erinnert mich an Modern C++ Design. 😕



  • ccquestions schrieb:

    Mich hätte mal interessiert, warum es eigentlich nicht möglich ist, Rückgabewerte zu überladen? Gibts da ne logische Erklärung?

    Weil es meistens unpraktisch ist.

    Man kann es aber machen, indem man ein Objekt returned, das einen (template) konvertierungs operator hat.

    class ReturnOverload {
    public:
       operator float() {
         return 1.5;
       }
       operator int() {
         return 2;
       }
    };
    
    ReturnOverload return1point5or2() {
       return ReturnOverload();
    }
    
    int main() {
       int i = return1point5or2();
       float f = return1point5or2();
    
       //cout << return1point5or2(); // dumme Sache sowas
    }
    


  • sadiufhasd schrieb:

    Gibt es, nur nicht (direkt) in C++. Nennt sich double dispatch. Kann man aber nachbauen 🙂 http://stackoverflow.com/questions/429849/double-dispatch-multimethods-in-c

    Hat jetzt mit der Frage des OP genau gar nix zu tun.



  • Shade Of Mine schrieb:

    ccquestions schrieb:

    Mich hätte mal interessiert, warum es eigentlich nicht möglich ist, Rückgabewerte zu überladen? Gibts da ne logische Erklärung?

    Weil es meistens unpraktisch ist.

    Man kann es aber machen, indem man ein Objekt returned, das einen (template) konvertierungs operator hat.

    class ReturnOverload {
    public:
       operator float() {
         return 1.5;
       }
       operator int() {
         return 2;
       }
    };
    
    ReturnOverload return1point5or2() {
       return ReturnOverload();
    }
    
    int main() {
       int i = return1point5or2();
       float f = return1point5or2();
    
       //cout << return1point5or2(); // dumme Sache sowas
    }
    

    Ganz edel, vielen Dank! Mit dem werd ich gleich ne Runde rumexperimentieren...

    Gruss



  • Reicht dir vielleicht auch einfach so etwas?

    #include <iostream>
    #include <sstream>
    
    template<typename T> T Convert(std::string const &in)
    {
        std::istringstream istr(in);
        T ret;
        istr >> ret;
        return ret;
    }
    
    int main()
    {
        string number_string = "123";
        double v1 = Convert<double>(number_string);
        double v2 = Convert<int>(number_string);
        std::cout << v1 << "\n" << v2 << "\n";
    }
    


  • wxSkip schrieb:

    Reicht dir vielleicht auch einfach so etwas?

    #include <iostream>
    #include <sstream>
    
    template<typename T> T Convert(std::string const &in)
    {
        std::istringstream istr(in);
        T ret;
        istr >> ret;
        return ret;
    }
    
    int main()
    {
        string number_string = "123";
        double v1 = Convert<double>(number_string);
        double v2 = Convert<int>(number_string);
        std::cout << v1 << "\n" << v2 << "\n";
    }
    

    Leider nein, ich bau mir grad nen assoziativen Container, der verschiedene Informationen mit verschiedenen Dateitypen enthält und der dann über eine uniforme Zugriffsmethode a la std::map je nach gefragtem Dateityp den richtigen Wert ausliest. Aber die stringsteam Klassen sind schon verdammt praktisch!

    Gruss



  • Das sollte nur ein Beispiel sein, ich bezog mich auf die Template-Spezifikation. Wenn du weißt, was du für einen Typ erwartest, kannst du ja trotzdem etwas à la ReadNode<MyType>(tree) machen.



  • Ja, entweder get<Type>(map, key) oder map[key].as<Type>() .
    Ist im Prinzip das selbe.
    Und auch ähnlich dem Vorschlag von Shade Of Mine, nur dass ich die impliziten Konvertierungsoperatoren weglassen würde, und statt dessen eine Getter-Funktion anbieten würde (die "as" Funktion in map[key].as<Type>() ).

    Ähnliche Funktionalität gibt es übrigens bereits fertig in der Boost, nennt sich boost::any .
    Damit könntest du einfach eine std::map<key_type, boost::any> machen. Wobei boost::any den Zugriff nur mit exakt dem selben Typ erlaubt der bei der Zuweisung verwendet wurde. Setzen mit short und auslesen als int geht also beispielsweise nicht. Genau so setzen mit Derived* und Auslesen als Base* geht IIRC auch nicht.



  • wxSkip schrieb:

    Das sollte nur ein Beispiel sein, ich bezog mich auf die Template-Spezifikation. Wenn du weißt, was du für einen Typ erwartest, kannst du ja trotzdem etwas à la ReadNode<MyType>(tree) machen

    Danke für die Ergänzung, ich hab' gerade tatsächlich nicht bis dahin gedacht 🙂 Das ist dann in dem Fall eine Frage des Abwägens was mich schöner dünkt: Arbeite ich mit einem Template und Spezialisierungen seh ich gleich im Code welche Funktion ich jetzt tatsächlich aufrufe, während beim Vorschlag von "Shade Of Mine" alles versteckt abläuft, was dann den Code im Gesamten unter Umständen übersichtlicher macht, weil nicht ganz so viele <Typenangaben> vorkommen.

    hustbaer schrieb:

    Ähnliche Funktionalität gibt es übrigens bereits fertig in der Boost, nennt sich boost::any .

    Ich habe mich bis jetzt bezüglich der boost Bibliothek und sogar der standard Bibliothek etwas zurückgehalten, da ich mich, auch wenn das alles verlockend klingt, noch selbst persönlich mit den Basics an der Quelle rumschlagen will, um so mein Verständnis zu verbessern. Gleich selbst aufwändigere Template Containerklassen etc zu schreiben hat mir persönlich jetzt trotz einiger immer wieder mal auftretender Probleme schon viel gebracht. Schreibe gerade zum ersten Mal an einem Programm, das den Namen auch wirklich langsam verdient hat... ( 10'000+ Zeilen ).

    Gruss



  • ccquestions schrieb:

    Salut zusammen
    [...]
    Mich hätte mal interessiert, warum es eigentlich nicht möglich ist, Rückgabewerte zu überladen? Gibts da ne logische Erklärung?
    Gruss

    Ja, der Grund ist, dass man Rückgabewerte nicht auswerten muss:

    int f();
    float f()
    T f()
    //....
    
    f(); //welches f soll genommen werden?
    

    In Sprachen, in denen der Rückgabewert für Überladungen herangezogen wird, gibt es i.d.R. eine strike Unterscheidung zwischen Prozeduren und Funktionen. Ada ist hierfür ein Beispiel.

    Der Rückgabewert einer Funktion MUSS ausgewertet werden:

    function F return Float;
    F; --Fehler: F gibt Wert zurueck -> muss zugewiesen werden
    

    Was auf der einen Seite manchmal nervig ist, erlaubt aber auch die Überladung nach Rückgabewert:

    function F return Float;
    function F return Integer;
    procedure F;
    
    F; --ruft procedure F auf
    
    Val : Integer := F; --ruft integer-Version auf
    --usw.
    


  • Tachyon schrieb:

    Ja, der Grund ist, dass man Rückgabewerte nicht auswerten muss:

    int f();
    float f()
    T f()
    //....
    
    f(); //welches f soll genommen werden?
    

    Das an sich wäre doch kein Problem: Es wäre für Compiler ein leichtes, einen Fehler auszugeben wenn die Funktion wie in obigem Fall nicht eindeutig zuzuordnen ist. Ist eine void Variante definiert, würde es darüber hinaus Sinn machen, dass dann diese verlinkt würde.


  • Mod

    Die Idee dahinter ist, dass der gleiche Aufruf immer das gleiche machen soll, unabhängig vom Kontext. Das wäre bei Überladenen von Rückgabewerten nicht mehr gegeben.



  • SeppJ schrieb:

    Die Idee dahinter ist, dass der gleiche Aufruf immer das gleiche machen soll, unabhängig vom Kontext.

    Du verwendest die Funktion time(NULL), die vom Kontext abhängt, also nicht gerne? 😃



  • wxSkip schrieb:

    SeppJ schrieb:

    Die Idee dahinter ist, dass der gleiche Aufruf immer das gleiche machen soll, unabhängig vom Kontext.

    Du verwendest die Funktion time(NULL), die vom Kontext abhängt, also nicht gerne? 😃

    Wo genau hängt time(NULL) vom Kontext ab?



  • Vom Kontext abhängen != Nicht immer das Gleiche zurückgeben



  • IMO ist es nicht das grösste Problem dass unterschiedliche Funktionen je nach Kontext aufgerufen würden, denn ähnliches kann in C++ schnell mal passieren.

    Beispielsweise wenn man einen operator + (A, 😎 und einen operator + (B, A) definiert. Dann wird bei a + b eine andere Funktion aufgerufen als bei b + a. Oder, ähnlicher Fall: man kann an vielen Stellen nicht sicher sein, ob z.B. der Copy-Ctor aufgerufen wird oder nicht (copy elision).

    Man ist als C++ Programmierer selbst dafür verantwortlich, dass Dinge die gleichbedeutend aussehen auch gleichbedeutend sind.

    Ich denke die ganzen impliziten Konvertierungen die in C++ erlaubt sind, sind hier eher störend.

    Auch hat in C++ jede Expression einen eindeutigen Typ, unabhängig davon in welchem Kontext sie verwendet wird. Ich könnte mir vorstellen, dass es bei Template-Programmierung zu einiger Verwirrung führen würde, wenn eine Expression je nachdem wo man sie einsetzt einen anderen Typ hat.
    Dinge wie decltype würden dadurch unmöglich, bzw. man bräuchte eine kompliziertere Syntax dafür.


  • Mod

    hustbaer schrieb:

    IMO ist es nicht das grösste Problem dass unterschiedliche Funktionen je nach Kontext aufgerufen würden, denn ähnliches kann in C++ schnell mal passieren.

    ähnlich Aussehen != gleich aussehen

    Insbesondere sogar
    ähnlich aussehen und unabhängig vom Kontext immer das gleiche tun != gleich aussehen aber abhängig vom Kontext unterschiedliches tun



  • Und?
    In C++ gibt es - wie erwähnt - sogar Fälle, wo 1:1 das gleiche in 1:1 dem gleichen Kontext dasteht, und der Compiler sich aussucht was er machen wird.
    So what?


Anmelden zum Antworten