Rückgabewert überladen



  • 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?



  • Da wir gerade bei impliziter Konvertierung / unerwartetem Aufruf sind:
    Ich hatte mal das Problem, dass ich mir eine Vektorklasse<T> geschrieben habe, die einen Konstruktor mit Argument size_t size = 0 hatte. Dazu habe ich zwei (globale) Operatoren definiert, einmal operator+(myvector<T>, myvector<T>) und einmal operator+(myvector<T>, T). (Das ist jetzt leicht vereinfacht, sollte aber nichts ausmachen). Nun habe ich eine Memberfunktion im Vektor, die den Typ T zurückgibt. Bei folgendem Code:

    wxString tmp;
    wxString tmp2 = tmp + "Hello World!";
    

    gab mir der GCC einen Fehler aus: Error: Function returning an array (in meiner Funktion, die den Typ T zurückgibt), ausgelöst vom Operator +. Lösung: Entweder meinen eigenen operator+() in AddVectors() umbenennen oder statt der rohen Zeichenkette wxString("Hello World!") zu schreiben.
    Kann sich das jemand erklären?



  • wxSkip schrieb:

    Kann sich das jemand erklären?

    Ins blaue rein geraten: dein Ctor war nicht explicit.



  • Shade Of Mine schrieb:

    wxSkip schrieb:

    Kann sich das jemand erklären?

    Ins blaue rein geraten: dein Ctor war nicht explicit.

    Nein, war er nicht. Aber wie willst du ein Array mit int-Konstruktor aus char* oder wxString erstellen?



  • @wxSkip:
    GCC hat Versucht den operator+(myvector<T>, T) mit T = char[13] zu instanzieren.
    Dazu musste er wiederum myvector<T> mit T = char[13] instanzieren.
    Wenn dann in operator+(myvector<T>, T) eine Funktion von myvector<T> verwendet wurde (direkt oder indirekt), die T als Returnwert hat, dann führt das zu dem Versuch ein Array zurückzugeben.

    Bin mir jetzt nicht 100% sicher, aber ich denke der Compiler muss zumindest mal myvector<char[13]> instanzieren, zumindest die Konstruktoren. myvector<char[13]> könnte ja einen Konvertierungs-Ctor haben, der mit wxString funktioniert. operator+(myvector<char[13]>, char[13]) wäre dann ein möglicher Kandidat für die Overload-Resolution. Und um herauszubekommen ob es wirklich so ist, muss GCC myvector<char[13]> eben erstmal instanzieren.

    Ob dabei der operator+(myvector<T>, T) mit T = char[13] noch instanziert werden muss, wenn nach dem Instanzieren von myvector<char[13]> bereits feststeht dass es keine implizite Konvertierung gibt, weiss ich nicht. Ob ein Fehler ala "Function returning an array" unter SFINAE fällt oder nicht, weiss ich auch nicht. Die SFINAE Regeln hab' ich nie so ganz behirnt - also was zu einem Übersetzungsfehler führt, und was nur nach SFINAE die Funktion aus dem Candidate-Set ausschliesst.

    EDIT: ich glaube mich vage zu erinnern, dass im Standard steht, dass sämtliche eventuell in Frage kommenden Template-Funktionen erstmal instanziert werden müssen. Angenommen ich irre mich diesbezüglich nicht, bleibt immer noch die Frage was mit SFINAE ist.



  • @hustbaer: Mir ist bloß schleierhaft, wieso er meint, er könne einen wxString in einen myvector<T> konvertieren. Schließlich gibt es bloß den Konstruktor myvector::myvector(size_t i = 0).
    Es müsste ja wxString zu myvector<const char[]> konvertiert werden und dass der Compiler das zweite Argument matcht und dann gleich alle Template-Funktionen des ersten Arguments instantiiert, glaube ich nicht.



  • Warum nicht boost::any und boost::any_cast benutzen?



  • EDIT: Hier stand Müll.



  • wxSkip schrieb:

    @hustbaer: Mir ist bloß schleierhaft, wieso er meint, er könne einen wxString in einen myvector<T> konvertieren. Schließlich gibt es bloß den Konstruktor myvector::myvector(size_t i = 0).

    Da myvector<T> ein Template ist, könnte erstmal grundsätzlich eine Konvertierung möglich sein.
    Könnte ja eine Spezialisierung myvector<char[13]> geben, oder myvector<char[N]> oder was auch immer. Und so eine Spezialisierung könnte einen ctor(wxString cosnt&) haben.

    Und um herauszufinden welche Konstruktoren die Template-Klasse myvector<char[13]> hat, muss der Compiler myvector<char[13]> instanzieren. Dabei werden natürlich nicht alle Memberfunktionen von myvector<char[13]> instanziert.

    Das Instanzieren von myvector<char[13]> ist auch nicht das Problem, sondern das Instanzieren des operator + Templates.

    Der Compiler muss nämlich auch alle eventuell in Frage kommenden operator + Templates instanzieren, damit er sein Overload-Set bilden kann, damit er mit dem fertigen Overload-Set dann die Overload-Resolution starten kann. Um rauszubekommen ob er einen "besten" Kandidaten findet, mehrere gleich gute, oder gar keine.

    Vermutlich ergibt sich das Problem dadurch, dass der operator+(myvector<T>, T) irgendwo eine (Member)funktion von myvector<T> verwendet, die T als Returntyp hat. Hab ich eigentlich schon geschrieben. Und da operator+(myvector<T>, T) mit T = char[13] instanziert wird, knallt es an der Stelle.

    Und, jetzt fällt es mir wieder ein: SFINAE "erlaubt" diesen Fehler nicht, da er sich (vermutlich) im Funktionsrumpf befindet, und nicht schon im "Prototyp" zuschlägt. (Zumindest ist das das Verhalten das ich öfters beobachtet habe, wie gesagt: SFINAE ist für mich ganz dunkle Magie, muss ich mich erst noch weiter damit befassen um das halbwegs gut zu überglicken)


Anmelden zum Antworten