c# style [] operator



  • Hallo,

    ist es möglich den [] operator so zu überladen, dass man im c# style nicht nur get sondern auch eine explizite set-funktion haben kann?

    object this[int index]
    {
       get{ return ...; }
       set{ myfunc(index, value); }
    }
    
    ... operator[](const int index)
    ...
    myfunc(index, value);
    ...
    

  • Mod

    Gib eine Referenz zurück. Denk an const-correctness (d.h. du möchtest wahrscheinlich eine const und eine non-const Version anbieten). Überhaupt ist eine Referenz hier besser, solange du nicht weißt, wie teuer ein object zu kopieren ist.



  • Das Problem ist, dass ich mehrere Datentypen anbieten möchte, da die Funktion auf eine alte C Fremdschnittstelle zugreift für die die Klasse lediglich ein Wrapper ist, daher kann ich keine Referenzen zurückgeben, da ich nach der Übergabe die Funktion qausi aufrufen muss.

    Oder kann ich eventuell ein Datenobjekt (struct) erzeugen und diese etwa so aufbauen

    operator=(/*whatever comes here*/)
    {
       SetNewValue();
    }
    
    operator int()
    operator double()
    operator ...()
    {
       GetTheValue();
    }
    

    oder gibt es da eine einfachere Möglichkeit?
    Ich möchte das gerne so machen, da die Daten innerhalb der Schnittstelle indiziert und durch strings benannt sind, daher bietet sich der Zugriff per .["name"] eben an.



  • Ich denke mal, das ist den Aufwand nicht wert, den man dafür treiben muss. Die Lesbarkeit des Codes wird darunter leiden:

    #include <iostream>
    
    namespace ns {
    
    struct proxy_base { void* ctx; }; // Kontext für C-Funktion
    
    template<class Tag> struct getter;
    template<class Tag> struct setter;
    
    #define PROXY_ATTRIBUTE(type_,name_,gettr_,settr_)            \
    const struct name_##_tag {} name_ = {};                       \
    template<> struct getter<name_##_tag> : proxy_base {          \
      operator type_() const { return gettr_; }                   \
    };                                                            \
    template<> struct setter<name_##_tag> : getter<name_##_tag> { \
      setter& operator=(type_ value) { settr_; return *this; }    \
    };
    
    PROXY_ATTRIBUTE(int,foo,(ctx ? 17 : 29),(std::cout << value << std::endl))
    PROXY_ATTRIBUTE(double,bar,(3.1415),(std::cout << value << std::endl))
    
    class clazz
    {
      void* ctx; // Kontext für C-Funktionen
    public:
      clazz() : ctx(0) {}
    
      template<class Tag>
      getter<Tag> operator[](Tag) const
      { getter<Tag> g; g.ctx = ctx; return g; }
    
      template<class Tag>
      setter<Tag> operator[](Tag)
      { setter<Tag> s; s.ctx = ctx; return s; }
    };
    
    } // namespace ns
    
    int main()
    {
      using namespace ns;
      clazz obj;
      std::cout << "obj[foo] ist " << obj[foo] << std::endl;
      std::cout << "obj[bar] ist " << obj[bar] << std::endl;
      obj[foo] = 99;
      obj[bar] = 2.718;
    }
    


  • Sowas in der Richtung habe ich schon, nur nicht als rückgabewert für operator[].
    Weil ich dieses ewige leidige getXY, setXY einfach satt habe habe ich mir kurzerhand accessor<> gebastelt.

    Diese nimmt einen Pointer auf einen Klassenmember und ein Flag für den Zugriff, eventuell kann ich da etwas ableiten oder so umbauen, dass ich tatsächlich sowas mit relativ wenig Aufwand realisieren könnte (ein Pointer ist ein minimales übel)



  • @krümelkacker
    Wozu dann überhaupt noch über operator[] gehen - da kann man gleich die Accessor-Klasse als public Member machen.
    Dann muss man nur mehr obj.foo = 99; schreiben. Und verpestet den global-namespace nicht.

    ps: Dass man dafür mit einem etwas aufgeblähten Objekt bezahlt ist klar. Ist halt die Frage ob die ganzen Zeiger wirklich stören. Bzw. wenn man unbedingt will könnte man sogar vom "this" des Accessor-Members auf das "this" des äusseren Objekts zurückrechnen. Ist zwar vielleicht "pfui", aber funktioniert sicher problemlos.



  • Pria schrieb:

    Sowas in der Richtung habe ich schon, nur nicht als rückgabewert für operator[].
    Weil ich dieses ewige leidige getXY, setXY einfach satt habe habe ich mir kurzerhand accessor<> gebastelt.

    Diese nimmt einen Pointer auf einen Klassenmember und ein Flag für den Zugriff,

    Ich dachte, du willst irgendwelche C-Funktionen für's Setten und Getten aufrufen. Wenn du doch schon ein passendes Datenelement zur Hand hast, was macht dann dein accessor anderes als eine Referenz?



  • krümelkacker schrieb:

    Ich dachte, du willst irgendwelche C-Funktionen für's Setten und Getten aufrufen.

    Ich will nicht, ich muss und der accessor ist mommentan "nur" darauf ausgelegt einen datenpointer auf eine member variable zu halten.
    Ich überlege mir gerade ob es nicht sinnvoller wäre tatsächlich komplett auf set und get methoden zu gehen und den accessor quasi als wrapper nach aussen zu benutzen.



  • Kannst du ein moeglichst einfaches aber komplettes Beispiel als Code posten?

    Ich wuerde es ja genau andersherum machen. Einen Modifier schreiben, der das Objekt als Parameter bekommt und auf diesem arbeitet.



  • Die accessorklasse (hier full-access) die ich derzeit verwende, natürlich deutlich verbesserungswürdig. Eventuell geh ich hier tatsächlich komplett auf set, get, methoden, auch wenn dies bedeutet, dass der Anwender dann noch zusätzlich 2 methoden haben muss oder ich biete es optional über eine spezialisierung an.

    template<typename T>
    	struct accessor<void,T,AccessorFlag::FullAccess>
    	{
    		T &ptr;
    
    		public:
    			accessor(T &ptr) : ptr(ptr){}
    
    			inline T get() {return ptr;}
    			operator T() {return (T) this->ptr;}
    			inline T operator() () { return ptr; }
    			inline T* operator-> () {return &ptr;}
    
    			inline void operator =(T value) {ptr = value;}
    			inline void operator() (T value) { ptr = value; }
    			inline void set(T value) {ptr = value;}
    	};
    

    Der indexer, den ich gerade mal eben hingerotzt habe

    template<class O, typename Index, typename T, void (O::*set) (Index, T), T (O::*get) (Index)>
    	struct indexer
    	{
    		O* owner;
    		Index index;
    
    		public:
    			indexer(Index index) : index(index) {};
    
    			inline T get() { return owner->get(index); }
    			operator T() { return (T) owner->get(index); }
    			inline T operator() () { return owner->get(index); }
    
    			inline void operator =(T value) { owner->set(index, value); }
    			inline void operator() (T value) { owner->set(index, value); }
    			inline void set(T value) { owner->set(index, value); }
    	};
    

    Die Schnittstellenfunktion sieht so aus

    void* GetParameter(const char* index);
    void SetParameter(const char* index, void* value);
    

    [EDIT]
    Wobei void* jeweils einen von 4 standarddatentypen int, double, float, short haben kann



  • Und wie benutzt du das ganze? Wie sieht eine minimale main-Fkt. aus? Bzw. wie sieht das aus, was du erreichen willst. Tut mir leid, mit C# kann ich nichts anfangen (und 'wie C#' ist demnach nichtssagend). Davon mal abgesehen, in C++ wird eben nicht wie in C# programmiert.



  • na ganz einfach, die accessoren in eine klasse packen

    class X
    {
       public:
          X() : MyInt(myInt) {}
          accessor<int, AccessorFlag::FullAccess> MyInt;
    
       private myInt;
    }
    
    main()
    {
       X xy;
       xy.MyInt = 20;
       int v = xy.MyInt;
    }
    

    Was ich über den Indexer erreichen möchte ist schlicht folgendes, wie es mittlerweile bei vielen Sprachen Standard ist

    class IndexerText
    {
       public:
         indexer<string, int, &GetParameter, &SetParameter> operator[] (string index)
         {
            indexer<string, int, &GetParameter, &SetParameter> i(index);
            return index;
         } 
    
       /*
       in c#
       int this[string index]
       {
          get{ return GetParameter(index); }
          set{ SetParameter(index, value); }
       }
       */
    }
    
    main()
    {
       IndexerTest t;
       t["test"] = 20;
       int v = t["test"];
    }
    

    btw. ich habe gerade mal (vergeblich) versucht die templates so zu ändern, dass ich lediglich über die parameteranzahl der argumente zwischen funktion und direktzugriff wechsel, hat allerdings nicht geklappt. Kann mir jemand sagen wie ich volgendes dilemma auflösen kann

    template<typename T, class O, void (O::*setPtr) (T), T (O::*getPtr) ()>
    	struct accessor
    	{
    		O* owner;
    
    		public:
    			_access(O* owner) : owner(owner){}
    
    			inline T get() {return (owner->*getPtr)();}
    			operator T() {return (T) (owner->*getPtr)();}
    			inline T operator() () { return (owner->*getPtr)(); }
    			inline T* operator-> () {return &(owner->*getPtr)();}
    
    			inline void operator =(T value) {(owner->*setPtr)(value);}
    			inline void operator() (T value) { (owner->*setPtr)(value); }
    			inline void set(T value) {(owner->*setPtr)(value);}
    	};
    
    	template<typename T>
    	struct accessor<T, void, NULL, NULL> //<- compiler error
    	{
    		T &ptr;
    
    		public:
    			_access(T &ptr) : ptr(ptr){}
    
    			inline T get() {return ptr;}
    			operator T() {return (T) this->ptr;}
    			inline T operator() () { return ptr; }
    			inline T* operator-> () {return &ptr;}
    
    			inline void operator =(T value) {ptr = value;}
    			inline void operator() (T value) { ptr = value; }
    			inline void set(T value) {ptr = value;}
    	};
    


  • Probier mal so (ungetestet)

    class DummyClass {};
    
    template<typename T>
    struct accessor<T, DummyClass, (void (DummyClass::*)(T)) 0, (T (DummyClass::*)()) 0>
    {
        T &ptr;
    
    ...
    

    Bzw. vermutlich sind die Casts nichtmal nötig.
    DummyClass ist aber sicher nötig, weil void kann nicht der Klassentyp eines Member-Function-Pointers sein.

    Ich würde aber eher ein eigenes Template verwenden.



  • Klappt leider immer noch nicht

    error C2754: 'accessor<T,DummyClass,0x0,0x0>': Eine teilweise Spezialisierung darf keinen abhängigen Nichttyp-Vorlagenparameter haben
    

    Ich würde aber eher ein eigenes Template verwenden.

    ich würds der Einfacheit halber gerne in ein und dem selben objekt anbieten wollen

    [Edit]

    Habe es nun nach ein wenig rumprobieren und etwas tricksen hingekriegt, sogar so, dass ich jetzt sämtliche Zugriffstypen und evtl. sogar den indexer drüber laufen lassen kann.

    template<typename T> struct _setget{};
    template<typename T, class O, void (O::*setPtr) (T), T (O::*getPtr) ()> struct _setgetf{};
    
    template<class A, long I> //<- alibi variable
    struct accessor;
    
    template<typename T, class O, void (O::*setPtr) (T), T (O::*getPtr) ()>
    struct accessor<_setgetf<T,O,setPtr,getPtr>,0>
    { ... };
    
    template<typename T>
    struct accessor<_setget<T>,0>
    { ... };
    

    und verwende dies im code nun folgendermaßen

    class X
    {
      public:
          X() : XY(xy) {}
          accessor<_setget<int>,0 /*muss leider sein, evtl per macro ersetzen*/ > XY;
    
      private:
          int xy;
    };
    
    main()
    {
       X x();
       x.XY = 20;
       cout << x.XY << endl;
    }
    

Log in to reply