lhs/rhs Unterscheidung dank Operator () Überladung?



  • Das sieht SEHR gut aus 🙂

    Schon nett wie man tricksen kann. Ich werds mal ausprobieren, danke schonmal. Btw., kostet das Performance? Klar es ist ne Zuweisungsoperation mehr, ein Konstruktor, ein Setter/getter. Würde mich interessieren ob der Compiler diese Zweitzuweisung und das temporäre Objekt wegrationalisieren kann. Aber auf jeden Fall schön.

    Kennt jemand noch ne schönere Lösung?



  • LudiKalell schrieb:

    Kennt jemand noch ne schönere Lösung?

    Dies ist imho die schönste Lösung. foo wird in diesem Fall als Proxy-Klasse bezeichnet.
    So macht es auch zB std::bitset beim op[].



  • Noch nicht getestet aber ne kleine Optimierung damit getValue nicht unnötigerweise jedesmal aufgerufen wird:

    template<class T>
    class Graph
    {
        template<class T>
        class foo
        {
            friend class Graph;
    
            public:
                operator T() const
                {
                    return graph->getValue( a, b);
                }
    
                void operator=( const T& val )
                {
                    graph->setValue( a, b, val );
                }
    
            private:
                foo( Graph* g, size_t a, size_t b, const T& val ) : graph(g), a(a), b(b)
                {
                }
    
                foo();
                foo( const foo& );
                void operator=( const foo& );
    
            private:
                size_t a, b;
                Graph* graph;
        };
    
        friend class foo<T>;
    
        T getValue( size_t a, size_t b) const
        {
            // gibt gespeicherten Wert oder Standardwert zurück
        }
    
        void setValue( size_t a, size_t b, const T& val )
        {
            // weist den Wert zu, die Datenstruktur vergrössert sich.
        }
    
    public:
        foo<T> operator()( size_t a, size_t b )
        {
            return foo<T>( this, a, b);
        }
    };
    

    Keine Ahnung ob der Code überhaupt so läuft, gerade keinen Compiler zur Hand. Ich muss auch zugeben dass ich noch nicht 100%ig durchsteige wann

    operator T() const
                {
                    return graph->getValue( a, b);
                }
    

    genau aufgerufen wird. Ich meine ich gebe ein unbenanntes temporäres Objekt raus, entweder ist es rechts oder links eines "=" bzw eben Paramter o.ä.
    Der Sytanx nach müsste also
    void operator=( const T& val )
    aufgerufen werden wenn dem Objekt etwas zugewiesen wird, *gelöscht* Ahhh jetzt weiss ich auch was T() const ist, der cast Operator oO. Wie Schuppen von den Augen..
    Also wird quasi bei ner Zuweisung der Zuweisungsoperator und danach der cast Operator angewandt(ich bin zu müde.. ), ansonsten nur der cast Operator, und demzufolge ist meine "Optimierung" fürn Arsch, richtig?(nein ist sie nicht du müder Klumpen) Ich verwirr mich selbst... aber jetzt hab ichs gerafft. So wie's da oben steht wird nur nen setValue oder nen getValue aufgerufen. Ausser man hat ne Verschachtelung wie
    i = g(2,4) = 3;



  • Man nennt das auch Proxy.
    Ist in (fast?) jeder vector<bool> Implementierung zu finden, und der Grund warum vector<bool> immer (fast immer?) ein "Spezialfall" ist, weil andere Regeln dafür gelten.
    z.B. geht dann sowas nimmer (was mit jedem anderen Typ ausser bool funktioniert):

    bool& b = vec[123];
    b = false;
    

    Den selben Effekt hast du dann auch, wobei das nicht negativ sein muss - gehört halt nur dokumentiert.



  • hustbaer schrieb:

    Man nennt das auch Proxy.
    Ist in (fast?) jeder vector<bool> Implementierung zu finden, und der Grund warum vector<bool> immer (fast immer?) ein "Spezialfall" ist, weil andere Regeln dafür gelten.
    z.B. geht dann sowas nimmer (was mit jedem anderen Typ ausser bool funktioniert):

    bool& b = vec[123];
    b = false;
    

    Deshalb benutzt man std::vector<bool>::reference statt bool& 😉



  • um nochmal zum eigentlichen Thema zurückzukommen:

    Es funzt, teilweise.

    Leider aber eben nicht völlig transparent, ein Beispiel:

    Graph<Klasse*> g;
    g(2,4) = new Klasse();
    
    g(2,4)->irgendnefunktion(); // FEHLER: "Basisoperand von »->« hat Nicht-Zeiger-Typ »Graph<Klasse*>::foo<Klasse*>"
    

    Ich nehm mal an ich muss für die Proxy Klasse auch den Operator -> definieren? Am besten noch gleich den . Operator. Aber nach ausführlicher Suche im inet nix dazu gefunden. Kann mir wer helfen?



  • LudiKalell schrieb:

    Ich nehm mal an ich muss für die Proxy Klasse auch den Operator -> definieren? Am besten noch gleich den . Operator. Aber nach ausführlicher Suche im inet nix dazu gefunden. Kann mir wer helfen?

    Den '.'-Operator kannst du nicht deklarieren, nur den "->"-Operator ( T* operator->(); ). Du musst dir halt nur überlegen, wie du das behandelst.



  • Am einfachsten wäre es (da der Proxy ja nur für einen einzigen Typen eingesetzt wird) die entsprechenden Methoden als Weiterleitung in der Proxyklasse zu implementieren:

    class Bla
    {
        class TollerProxy
        {
        public:
           void abgefahrene_methode () const
           {
               BlaZeiger->getValue(bla, bla).abgefahrene_methode();
           }
    //...
        };
    //...
    };
    


  • Ok habs gefunden..

    T operator ->() const  {
       return graph->getWeight( a, b);
    }
    

    Ok den . Operator kann ich nicht überladen..
    Folgendes Codebeispiel

    Graph< pair <double,double> > g;
    g(2,4) = pair<double, double>(3, 4);
    
    g(2,4).first = 3; // "Fehler: »class Graph_UW<std::pair<double, double> >::foo<std::pair<double, double> >« hat kein Element namens »first«
    

    Irgend ne Möglichkeit das zu maskieren bzw ihn zu zwingen ZUERST zu casten und dann . zu verwenden?

    Und gerade noch ein Problem gefunden mit

    Graph< baseclass* > g(10,NULL);
    dynamic_cast<derived_class*>(g(2,4))->some_member_of_derived_class(); //"Fehler: ungültiges dynamic_cast vom Typ »Graph<baseclass*>::foo<baseclass*>« in den Typ »derivedclass*«
    

    Wenn ich das umschreibe als

    baseclas* b = g(2,4);
    dynamic_cast<derived_class*>(b)->some_member_of_derived_class();
    

    funzt es natürlich. Wenn ich das ganz am Beispiel von static_cast<double>(Graph< int >) mache nörgelt er nicht.

    Edit:
    Das passiert wenn man zu lange am Text schreibt 😉 danke..
    Und nein, der Proxy wird leider jetzt schon für 3 verschiedene Typen eingesetzt und soll so in eine Bibliothek übernommen werden. Dafür natürlich möglichst transparent. Mit den bald mal im C++ Standrad aufgenommenen template "Abfragen" könnte man vielleicht zwischen pointer und normalem Typ unterscheiden, aber derzeit ist das wirklich hässlich.



  • LudiKalell schrieb:

    Irgend ne Möglichkeit das zu maskieren bzw ihn zu zwingen ZUERST zu casten und dann . zu verwenden?

    Ich glaube das mit dem casten hast du nicht ganz verstanden.. Wenn du mit g(2,4)=... drauf zugreifst, wird nichts gecastet, es wird einfach nur der operator= aufgerufen, der die rechte Seite des Gleich-Zeichens übergeben bekommt und die setXXX-Methode des Graphen aufruft.

    LudiKalell schrieb:

    g(2,4).first = 3;
    

    Nochmal: Den Operator "Punkt" kannst du nicht überladen, die Proxy-Klasse hat keinen Member namens "first", deshalb klappt auch g(2,4).bla nicht! Wenn du den Pfeil-Operator gut überladen hast, kannst du mit g(2.4)->first += 1389; drauf zugreifen, dafür ist er ja da.



  • Ich hab meinen text vom letzten Post nur stehen lassen, siehe edit, hatte deinen Text gelesen.
    Ja dass bei dem Beispiel nur der Zuweisungsoperator ne Rolle spielt hab ich übersehn. Hatte das mit dem cast etc. schon cverstanden, ich hab überall couts gesetzt und seh welche Funktion wann aufgerufen wird für verschiedene Szenarien weil ich mir anfangs nich sicher war.

    Dennoch: Ich benutze den Graphen für Pointer genauso wie für normale Objekte. Demzufolge
    T operator->() const
    Dieser hat die erwünschte Auswirkung für Pointer Objekte. Man muss nicht wissen dass da ein Pointer dahintersteckt. Und ich benutz sehr oft Pointer..
    Die Verwendung ist also transparent. Für normale Objekte gibts dann den Compilefehler.. Nagut, ich könnte.. hrmm..
    T operator->() const
    zusätzlich überladen. Und definieren dann operator ->() eben nen T
    zurückgibt.
    Dann bei Pointer ->* benutzen und sonst statt . nen ->()

    Ne bessere Lösung möglich?

    edit: oder ich schreib 2 Varianten, eine für Pointer, eine für Objekte, und überprüfe im Constructor irgendwie dass sie entsprechend falsch verwendet werden. Was aber glaube ich nicht geht.. obwohl.. doch.
    Müsste ja im Construktor schon nen this haben und damit mal probeweise son -> operator anwenden, dann sollte der Compiler missbrauch verbieten.



  • Gut ich lege das Problem lieber bei Seite. Es scheint keine wirklich gute Lösung zu geben für den . Operator wenn man Transparenz möchte. Hab nun ne at() Funktion für den speziellen Fall.

    Aber dann bleibt immer noch folgendes evtl. lösbares Problem:

    Graph< baseclass* > g(10,NULL);
    baseclass* b;
    dynamic_cast<derived_class*>(g(2,4))->some_member_of_derived_class(); //"Fehler: ungültiges dynamic_cast vom Typ »Graph<baseclass*>::foo<baseclass*>« in den Typ »derivedclass*«
    dynamic_cast<derived_class*>(b = g(2,4))->some_member_of_derived_class(); //funzt!
    

    Das passiert auch mit C cast, static_cast, reinterpret_cast, ...



  • Den ->* Operator kannst Du auch nicht überladen, ausserdem sähe das vermutlich syntaktisch gräßlich aus 🙂

    Über Spezialisierung wäre sowas machbar:

    template<class T> // macht aus T einen zeiger für alle nicht-zeigertypen
    struct graph_pointer_helper
    {
        typedef T* type;
        static type pointer( T& value ) { return &value; }
    };
    
    template<class T> // spezialisierung für zeiger reicht nur durch
    struct graph_pointer_helper<T*>
    {
        typedef T* type;
        static type pointer( T* value ) { return value; }
    };
    
    template<class T>
    class Graph
    {
    public:
        explicit Graph( T const& value = T() ) : value( value ) {}
    
        typename graph_pointer_helper<T>::type operator->() { return graph_pointer_helper<T>::pointer( value ); }
    
    private:
        T value;
    };
    
    int main()
    {
        Graph<string> a;
        a->assign( "hallo" );
    
        Graph<string*> b( new string );
        b->assign( "hallo" );
    }
    


  • LordJaxom schrieb:

    Den ->* Operator kannst Du auch nicht überladen

    Ich denke, das ist möglich...



  • Naja ->* ist schon überladbar, sagt MSDN, das inet und auch mein Compiler 😉

    Ja der Code sieht schön aus, nur leider halt Spezialisierung, der Graph soll wie gesagt alles möglich an verschiedenen Klassenpointern nehmen(gut das ist nicht das Problem, da funzt T operator->() ja einwandfrei und transparent) und eben auch noch Objekte. Gut für die Objekte könnte man dann ne Spezialisierung einfügen, aber so wie ich das verstehe muss ich die Spezialisierung dann auch für alle Pointertypen definieren.
    Und selbst wenn nicht: es soll einfach funktionieren, für jeden Typen.

    Ich habe glaube mittlerweile eine zumindest für mich befriedigende Lösung gefunden:
    Da der Anwender eh schonmal so oder so nicht drum rumkommt die Klasse wenigstens etwas zu kennen (-> für jeglichen Memberzugriff und kein operator. erlaubt oder ähnliches) hab ich einfach folgendes festgelegt:

    const T& operator() (size_t,size_t)
    foo<T>& operator [](size_t a)
    foo<T>& foo<T>::operator [](size_t B) {b = B; return *this;}
    T& at(size_t)
    // Schreibzugriff:
    g[i][j] = x; 
    // Lesezugriff:
    x = g(i,j);
    // cast:
    x = static_cast<class*>(g(i,j));
    //Memberzugriff wenn T Pointertyp
    g[i][j]->blub(); 
    //Memberzugriff wenn T kein Pointertyp
    g(i,j).blub(); //wenn blub() const
    g.at(i,j).blub(); //sonst
    

    Ist zwar an der Grenze der Transparenz, aber da der User eh den header anschaun muss noch ok..

    Wenn sonst keine weiteren Vorschläge kommen dann nochmal
    DANKE DANKE DANKE
    an alle und vor allem an Badestrand 👍



  • LudiKalell schrieb:

    Ja der Code sieht schön aus, nur leider halt Spezialisierung, der Graph soll wie gesagt alles möglich an verschiedenen Klassenpointern nehmen(gut das ist nicht das Problem, da funzt T operator->() ja einwandfrei und transparent) und eben auch noch Objekte. Gut für die Objekte könnte man dann ne Spezialisierung einfügen, aber so wie ich das verstehe muss ich die Spezialisierung dann auch für alle Pointertypen definieren.
    Und selbst wenn nicht: es soll einfach funktionieren, für jeden Typen.

    Tut es doch, die Spezialisierung spezialisiert ja nur für Pointer. Für alle anderen Typen gilt das Basistemplate. Einschränkungen auf einen bestimmten Typ sehe ich hier nicht.



  • Spezialisierung für Pointer wäre grauenhaft. Wir verwenden alle möglichen Basispointer und Pointer auf Subklassen, und es kommt noch Kram dazu. Locker 10 Spezialisierungen wären da nötig, und der Code bläht sich auf, evtl. auch die Compilezeit? Naja ich behalt's mal im Kopf, vielleicht ist das am Ende die beste Lösung.


Anmelden zum Antworten