lhs/rhs Unterscheidung dank Operator () Überladung?
-
Ich hoffe der Titel ist aussagekräftig..

Schonmal vielen Dank für die Mühe an die Experten hier..Es geht sich um folgendes..
Grundsätzlich will ich aus extremen Speicherproblemene heraus einen ungerichteter kantengewichteter Graph NICHT als Matrix darstellen sondern eben als Vektor von Adjazenzlisten.
Problem ist: Ich mache das mit einem vector< map< size_t, T > > und
möchte trotzdem Tranzparenz erzeugen, das soll heissen:der Nutzer soll möglichst durch einen operator () (size_t i, size_t j) auf die Elemente zugreifen können (gerne auch [][]) ...
Graph<size_t> g(10,INT_MAX); // 10 ist Anzahl Knoten, INT_MAX ist default Kantengewicht g(2,4) = 5; // A size_t m = g(2,4); // B size_t n = g(1,5); // C, wobei folgendes Verhalten erwünscht ist:
A soll eine andere Funktion aufrufen als B und C!
A weist den Wert zu, die Datenstruktur vergrössert sich.
B ruft diesen Wert ab und gibt 5 zurück.
C erkennt dass es den Wert nicht gibt und gibt statt dessen einen Standardwert zurück(INT_MAX), der beim Konstructor benutzt wurde. Die Struktur vergrössert sich dadurch NICHTMeine Vermutung war dass das ganze so aussieht:
linksseitige Nutzung, sprich Zuweisung, durch:
T& operator() (const size_t i, const size_t j)
rechtsseitige Nutzung durch
T operator() (const size_t i, const size_t j) constAber nein.. sobald ich
T& operator() (const size_t i, const size_t j)
definiert habe benutzt er diesen IMMER, leider, auch rechtsseitig, und ich kann ihn nicht mehr davon abhalten zwischen Zuweisung und rein passiver "Abfrage" zu unterscheiden.Gut mag man sagen, das ist nicht so schlimm, dann definiert man halt diesen Operator zB so:
*.hh: std::vector< map < size_t, T > graph; T undefined; Graph_UW(size_t nodeCount = 0, T undefined = T()); ... *.cc: template<class T> Graph_UW<T>::Graph_UW(size_t nodeCount, T undefined) : graph(nodeCount), undefined(undefined) { } template<class T> T& Graph_UW<T>::operator ()(size_t n1, size_t n2) { if(graph[min(n1,n2)].count(max(n1,n2))==1) return graph[min(n1,n2)][max(n1,n2)]; else return undefined; // min und max weil ich keine symmetrischen Adjazenzlisten // speichere sondern nur jede Adjazenz einmal als graph[i][j], i < j }Aber damit bin ich alles andere als glücklich. Denn dadurch verändere ich undefined und das ist absolut nicht mein Ziel. undefined sollte natürlich const sein, aber dann kann ich es natürlich wiederrum nicht zurückgeben, und
const T& Graph_UW<T>::operator ()(size_t n1, size_t n2)erlaubt natürlich keine Zuweisung, ein Riesen Bockmist also.
Kann ich irgendwie zwischen rechtsseitiger und linksseitiger Nutzung unterschieden? Dann würde ich bei Zuwesiung einfach
return graph[min(n1,n2)][max(n1,n2)];aufrufen und bei rechtsseitiger Nutzung den check verwenden und const T zurückgeben.
Vielen vielen Dank schonmal (warum wird vo-rraus geblockt? oO)

-
LudiKalell schrieb:
Kann ich irgendwie zwischen rechtsseitiger und linksseitiger Nutzung unterschieden?
Soweit ich weiß, geht das nicht.
Möglicherweise etwas umständlich, aber als Workaround könntest du auch ein Zwischen-Objekt zurückgeben, dass sich je nach Operation (operator= vs T-cast) anders verhält.
-
Ich kann natürlich, das war mein ursprünglicher Ansatz, einfach ein pair< bool, T > zurückgeben und über den boolschen Wert prüfen ob der T Wert koscher ist, aber wie gesagt ging es mir vor allem um die Tranzparenz da ich eine schon verwendete Matrixklasse ersetzen möchte. Und einfach weil der kleine Perfektionist in mir der meint das "müsste doch gehen" mich nicht in Ruhe lässt....
-
LudiKalell schrieb:
Ich kann natürlich, das war mein ursprünglicher Ansatz, einfach ein pair< bool, T > zurückgeben und...
Ne, so meinte ich das nicht

Ich skizziere einfach mal (schnell und ungeprüft):
template<class T> class Graph { template<class T> class foo { friend class Graph; public: operator T() const { return value; } 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), value(val) { } foo(); foo( const foo& ); void operator=( const foo& ); private: size_t a, b; Graph* graph; T value; }; 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, getValue(a,b) ); } };Evtl gibt's noch ne einfachere Version, ist im Moment (is ja schon spät) das einzig sinnvolle, was mir einfällt...
edit: Dadurch, dass Graph::foo private ist, braucht man Graph::foo::foo() + Copy-Constructor + Zuweisungsoperator gar nicht explizit private zu machen, sollte auch so ziemlich Missbrauchs-sicher sein.
-
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 CodebeispielGraph< 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).blanicht! Wenn du den Pfeil-Operator gut überladen hast, kannst du mitg(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(); //sonstIst 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.