2010-04-23 9 views
35

Ich war Abschnitt 13,5 nach refuting the notion, dass eingebaute Operatoren nicht in Überladung Auflösung teilnehmen, und bemerkte, dass es keinen Abschnitt auf operator->*. Es ist nur ein generischer binärer Operator.Sind freie Betreiber -> * Überlasten böse?

Seine Brüder, operator->, operator*, und operator[], müssen alle nicht-statische Elementfunktionen sein. Dies schließt die Definition einer Überladung einer freien Funktion für einen Operator allgemein aus, der verwendet wird, um eine Referenz von einem Objekt zu erhalten. Aber die seltene operator->* ist weggelassen.

Insbesondere hat operator[] viele Ähnlichkeiten. Es ist binär (sie haben eine goldene Gelegenheit verpasst, es n-ary zu machen), und es akzeptiert eine Art von Container auf der linken Seite und eine Art von Locator auf der rechten Seite. Der Abschnitt zu Sonderregeln, 13.5.5, scheint keine tatsächlichen Auswirkungen zu haben, außer um freie Funktionen zu verbieten. (Und diese Beschränkung schließt auch Unterstützung für commutativity!)

So zum Beispiel, das ist perfectly legal:

#include <utility> 
#include <iostream> 
using namespace std; 

template< class T > 
T & 
operator->*(pair<T,T> &l, bool r) 
    { return r? l.second : l.first; } 

template< class T > 
T & operator->*(bool l, pair<T,T> &r) { return r->*l; } 

int main() { 
     pair<int, int> y(5, 6); 
     y->*(0) = 7; 
     y->*0->*y = 8; // evaluates to 7->*y = y.second 
     cerr << y.first << " " << y.second << endl; 
} 

Es ist einfach, Anwendungen zu finden, aber alternative Syntax neigt nicht so schlimm sein. Zum Beispiel skaliert Indizes für vector:

v->*matrix_width[2][5] = x; // ->* not hopelessly out of place 

my_indexer<2> m(v, dim); // my_indexer being the type of (v->*width) 
m[2][5] = x; // it is probably more practical to slice just once 

Hat der Normenausschuss vergessen, dies zu verhindern, war es zu hässlich als zu stören, oder gibt es reale Anwendungsfälle?

+0

Ein Datenpunkt: Coneau (http://www.comeaucomputing.com/tryitout/) lehnt den Code selbst, nachdem ich entfernt haben '' und der erste Betreiber: 'Fehler: kein Operator„-> * "Entspricht diesen Operanden" – sbi

+1

@sbi: Comeau war der erste Ort, an den ich gegangen bin. Ich habe immer noch diesen Tab offen ... der einzige Code, den ich vor dem Wechsel zu GCC 4.5 eingegeben habe, war "struct x {int y;}; int & ope rator -> * (x & l, int r) {return l.y; } void f() { x q; int & i = q -> * 3; } ". - und es gibt Erfolg für das Hauptbeispiel minus alles abhängig von dieser ersten type_traits abhängigen Überlastung. – Potatoswatter

+0

Ich weiß nicht, warum Operator -> * kann so überlastet werden, aber es sieht hella hässlich! Ich würde Finger weg von ihm aus dem gleichen Grund wie das Überladen von Komma - es sieht nicht wie intuitives C++ aus. – AshleysBrain

Antwort

2

Googeln um ein bisschen, fand ich mehr Instanzen von Menschen fragen, ob operator->* jemals als tatsächliche Vorschläge verwendet wird.

Ein paar Orte empfehlen T &A::operator->*(T B::*). Nicht sicher, ob dies die Absicht des Designers oder einen falschen Eindruck widerspiegelt, dass T &A::operator->*(T A::*) ein eingebauter ist. Nicht wirklich mit meiner Frage verwandt, aber gibt eine Vorstellung von der Tiefe, die ich in der Online-Diskussion & Literatur gefunden habe.

Es gab eine Erwähnung von "D & E 11.5.4", die ich Design und Evolution von C++ vermute. Vielleicht enthält das einen Hinweis. Ansonsten werde ich nur feststellen, dass es ein bisschen nutzlose Hässlichkeit ist, die von der Standardisierung übersehen wurde, und die meisten anderen auch.

Bearbeiten Siehe unten für eine Paste der D & E Zitat.

Um dies quantitativ darzustellen, ist ->* der engste Bindungsoperator, der durch eine freie Funktion überlastet werden kann. Alle Überladungen von Postfix-Ausdruck und unären Operatoren erfordern nicht statische Elementfunktionssignaturen. Die nächste Priorität nach unären Operatoren sind C-artige Umwandlungen, von denen man sagen könnte, dass sie Umwandlungsfunktionen (operator type()) entsprechen, die auch keine freien Funktionen sein können. Dann kommt ->*, dann Multiplikation. ->* könnte wie [] oder wie % gewesen sein, könnten sie in beide Richtungen gegangen sein, und sie wählten den Pfad von EEEEEEVIL.

+0

Beantwortet dies Ihre Frage? Ich wusste nicht, dass wir unsere eigene Antwort akzeptieren können :) –

+0

@ Vicente: Ich überlegte, Ihre zu akzeptieren, aber es sagt immer noch fälschlicherweise, dass Nicht-Mitglied 'Operator->' erlaubt ist und die Begründung (oder 2) für die Erlaubnis 'Operator + = 'aber nicht erlaubt' operator = 'wurde in den Kommentaren ausgearbeitet. Und ich war nicht wirklich auf der Suche nach (Un-) Zustimmung, die Frage war: * Hat das Normenkomitee vergessen, dies zu verhindern, wurde es als zu hässlich angesehen oder gibt es echte Anwendungsfälle? *, Was eine Frage von einige Hinweise oder Beweise zu finden, um eine der Schlussfolgerungen zu stützen. – Potatoswatter

+0

Ich bin nicht wirklich glücklich mit dieser Selbst-Antwort, aber ich wurde angespornt durch einen Downvote (es kann unhöflich sein, eine offene Frage so lange unbeantwortet zu lassen) und es scheint einen Mangel an Beweisen zu geben, der dies unterstützen würde die Schlussfolgerung, dass es übersehen wurde. – Potatoswatter

1

Standard (Working Draft 2010-02-16, § 5.5) sagt:

The result of an ->* expression is an lvalue only if its second operand is a pointer to data member. If the second operand is the null pointer to member value (4.11), the behavior is undefined.

Sie können dieses Verhalten gut definierte sein wollen. Überprüfen Sie beispielsweise, ob es sich um einen Nullzeiger handelt, und behandeln Sie diese Situation. SO ich quess ist es richtige Entscheidung für einen Standard zu erlauben -> * Überladung.

+2

Abschnitt 5 gilt nicht für Überladungen. Zum Beispiel kann ein 'operator /' overload Verhalten für seinen zweiten Operanden definieren, der null ist. und Sie können das legal verwenden. – Potatoswatter

6

Ich stimme mit Ihnen überein, dass es eine Inkohärenz auf dem Standard gibt, es erlaubt keine Überlastung von operator[] mit Nichtmitgliedsfunktionen und ermöglicht es für operator->*. Aus meiner Sicht ist operator[] zu Arrays wie operator->* zu Strukturen/Klassen (ein Getter). Mitglieder eines Arrays werden mit einem Index ausgewählt. Mitglieder einer Struktur werden mithilfe von Elementzeigern ausgewählt.

Das Schlimmste ist, dass wir ->* statt operator[] zu verwenden versucht sein, können ein Array wie Element

int& operator->*(Array& lhs, int i); 

Array a; 

a ->* 2 = 10; 

Es ist zu erhalten auch eine andere mögliche Inkohärenz. Wir können eine Nichtmitgliedfunktion benutzen, um 10 und den Operator des Formulars @= zu überlasten und wir können es nicht für operator= tun.

ich nicht wirklich wissen, was der Grund dafür ist, das die folgende Recht

struct X { 
    int val; 
    explicit X(int i) : val(i) {} 
}; 
struct Z { 
    int val; 
    explicit Z(int i) : val(i) {} 
}; 
Z& operator+=(Z& lhs, const X& rhs) { 
    lhs.val+=rhs.val; 
    return lhs; 
} 

Z z(2); 
X x(3); 
z += x; 

und abweisend

Z& operator=(Z& lhs, const X& rhs) { 
    lhs.val=i; 
    return lhs; 
} 

z = x; 

Leider nicht antwortet auf Ihre Frage, aber das Hinzufügen noch mehr Verwirrung zu machen.

+1

'operator ->' und 'operator @ =' müssen Memberfunktionen sein. Freie Funktionen können von Ihrem Compiler unterstützt werden, aber das ist sicherlich nicht standard. – Potatoswatter

+0

operator @ = kann von Nichtmitglied überladen werden Funktionen Siehe Standard –

+2

§13.5.3 unterscheidet nicht zwischen den Zuweisungsoperatoren und besagt, dass "ein Zuweisungsoperator durch ein nicht-statisches memb er Funktion mit genau einem Parameter. "§5.17 sagt" Es gibt mehrere Zuweisungsoperatoren, die alle von rechts nach links gruppiert sind. "Wenn es etwas gibt, das mir fehlt, sollte" operator = "nicht speziell sein. – Potatoswatter

13

Das beste Beispiel, das mir bekannt ist, ist Boost.Phoenix, die diesen Operator überlädt, um faulen Memberzugriff zu implementieren.

Für jene nicht vertraut mit Phoenix ist es eine höchst raffinierte Bibliothek für Gebäude Akteure (oder Funktionsobjekte), die wie normale Ausdrücke aussehen:

(arg1 % 2 == 1)  // this expression evaluates to an actor 
       (3); // returns true since 3 % 2 == 1 

// these actors can also be passed to standard algorithms: 
std::find_if(c.begin(), c.end(), arg1 % 2 == 1); 
// returns iterator to the first odd element of c 

sie die oben erreicht durch operator% und operator== Überlastung. - angewendet auf den Aktor arg1 geben diese Operatoren einen anderen Aktor zurück. Die Palette der Ausdrücke, die auf diese Weise gebaut werden kann, ist extrem:

// print each element in c, noting its value relative to 5: 
std::for_each(c.begin(), c.end(), 
    if_(arg1 > 5) 
    [ 
    cout << arg1 << " > 5\n" 
    ] 
    .else_ 
    [ 
    if_(arg1 == 5) 
    [ 
     cout << arg1 << " == 5\n" 
    ] 
    .else_ 
    [ 
     cout << arg1 << " < 5\n" 
    ] 
    ] 
); 

Nachdem Sie für eine kurze Zeit mit Phoenix (und nicht, dass Sie jemals zurückgehen) Sie werden versuchen, so etwas wie dieses:

typedef std::vector<MyObj> container; 
container c; 
//... 
container::iterator inv = std::find_if(c.begin(), c.end(), arg1.ValidStateBit); 
std::cout << "A MyObj was invalid: " << inv->Id() << std::endl; 

Das wird fehlschlagen, weil Phoenix's Schauspieler natürlich kein Mitglied ValidStateBit haben. Phoenix wird um diese durch operator->* Überlastung:

(arg1 ->* &MyObj::ValidStateBit)    // evaluates to an actor 
           (validMyObj); // returns true 

// used in your algorithm: 
container::iterator inv = std::find_if(c.begin(), c.end(), 
     (arg1 ->* &MyObj::ValidStateBit) ); 

operator->* ‚s Argumente sind:

  • LHS: Schauspieler MyObj *
  • RHS Rückkehr: Adresse eines Mitglieds

Es gibt ein Akteur, der die LHS auswertet und nach dem angegebenen Member sucht.(NB: Sie wirklich, wirklich wollen sicherstellen, dass arg1 gibt MyObj * zurück - Sie haben nicht einen massiven Vorlage Fehler gesehen, bis Sie etwas falsch in Phoenix bekommen. Dieses kleine Programm generiert 76.738 Zeichen der Schmerzen (Boost 1.54, gcc 4.6):

#include <boost/phoenix.hpp> 
using boost::phoenix::placeholders::arg1; 

struct C { int m; }; 
struct D { int n; }; 

int main() { 
    (arg1 ->* &D::n) (new C); 
    return 0; 
} 
+0

Ausgezeichnet, danke fürs Teilen! Diese Verwendung schlägt jedoch eine Implementierung als eine kanonische Mitgliedsfunktion vor, wohingegen die Frage spezifischer auf nicht-Mitglieds (freie) Funktionen gerichtet ist. – Potatoswatter

+1

Ich habe überprüft: Die Boost-Implementierung verwendet eine Nicht-Member-Funktion in einem separaten Anti-ADL-Namespace. Dies geschieht möglicherweise für die allgemeine Best Practice und Einheitlichkeit mit den anderen Betreibern. – Potatoswatter