Überladung von Operatoren in C++ (Teil 1)
-
Ich würde das hier mit const schreiben, also statt
X operator+(X const& lhs, X const& rhs)
das hier
const X operator+(X const& lhs, X const& rhs)
Dann kann man nicht "(a+b)=c" schreiben.
-
*Edit
Sry, habe etwas in den falschen Thread kopiert.
-
Sehr guter Artikel.
Solltest du gewillt sein, an der ein oder anderen Stelle etwas ausführlicher zu werden, würde ich mich sehr darüber freuen.
Mir ist allerdings etwas aus deinem Beispiel aufgefallen. Folgender Code dürfte nicht ganz richtig sein, schließlich hast du extra den Konstruktor programmiert, verwendest ihn aber nicht. Bei der Erzeugung von a, b und c solltest du noch Standardwerte nachreichen, ansonsten kommt bei der Addition nur Mumpitz raus.
class Rational { public: Rational(int i); //Konstruktor für Konvertierungen von Ganzzahlen Rational operator+(Rational const& rhs) const; }; Rational a, b, c; int i; a = b + c; //ok, keine Konvertierung nötig a = b + i; //ok, implizite Konvertierung des zweiten Arguments a = i + c; //FEHLER: erstes Argument kann nicht konvertiert werden, da operator+ keine freie Funktion
-
nuke1600 schrieb:
schließlich hast du extra den Konstruktor programmiert, verwendest ihn aber nicht.
Doch, er wird für die implizite Konvertierung von
int
nachRational
verwendet.nuke1600 schrieb:
Bei der Erzeugung von a, b und c solltest du noch Standardwerte nachreichen, ansonsten kommt bei der Addition nur Mumpitz raus.
Der Code würde gar nicht kompilieren, weil kein Defaultkonstruktor vorhanden ist. Aber wie die Objekte erzeugt werden, ist in dem Beispiel nebensächlich...
-
Nexus schrieb:
wie die Objekte erzeugt werden, ist in dem Beispiel nebensächlich...
Richtig. Ein weitgehend vollständiges beispiel für so eine Rational-Zahlenklasse findest du im zweiten und dritten Teil des Artikels.
-
Btw, jetzt mit C++11 können einige Operatoren wie
operator?:
überladen werden. Kommt dazu noch was?
-
Sone schrieb:
Btw, jetzt mit C++11 können einige Operatoren wie
operator?:
überladen werden. Kommt dazu noch was?Wo hast du das den her?
-
pyhax schrieb:
Sone schrieb:
Btw, jetzt mit C++11 können einige Operatoren wie
operator?:
überladen werden. Kommt dazu noch was?Wo hast du das den her?
Mein Fehler, hab mich im Standard verguckt. Siehe 13.6.24, war schon ne Weile her, hab mich falsch erinnert...
-
Mmh, wäre es möglich, Beispiele für die Überladung von operator-> und operator->* zu bekommen? Insbesonders, da das hier anders als der Artikel zu behaupten scheint, dass typ * operator->() für den Zugriff auf Elemente von typ gedacht ist (mag aber auch einfach nur schlecht formuliert sein).
... Kann mir nicht recht vorstellen, wie man das zu verwenden hat.
Ich glaube, dass ich damit elegant dieses Ding hier vereinfachen könnte:
typedef struct Nest { double wert; double * koordinaten; double * wertigkeiten; } Nest * LeeresNest(const size_t & dimension, const size_t & bewohner) { Nest * ausgabe = (Nest *)operator new(2 * sizeof(double *) + (1 + dimension + bewohner) * sizeof(double)); ausgabe->koordinaten = (double *)(&ausgabe->wertigkeiten + 1); ausgabe->wertigkeiten = ausgabe->koordinaten + dimension; return ausgabe; }
(siehe dieses Thema, falls die Motivation kümmert)
Weiterhin:
Wenn ich mir diesen überladenen Operator anschaue:vector<T,Allocator>& operator= (const vector<T,Allocator>& x);
dann wird derselbe so benutzt:
vector a; vector b = a;
Mit diesem hier:
void* operator new (std::size_t size) throw (std::bad_alloc);
sollte die Verwendung also dergestalt aussehen:
Nest * (Nest *)new 2 * sizeof(double *) + (1 + dimension + bewohner) * sizeof(double);
Funktioniert aber nicht - die Syntax aus dem oberen Beispiel ist erforderlich. Wie kommt das?
Gleichsam wird aus
void* operator new (std::size_t size, void* ptr) throw();
in der Anwendung nicht etwa
new <wert> <zeiger>;
sondern eines von diesen beiden:
operator new(<wert>, <zeiger>); // keine Ahnung, wie hier der Konstruktor zu wählen wäre new (<zeiger>) <Konstruktor>;
Praktisch. Aber woher!?
-
Der string literal operator fehlt.
-
Du meinst wohl user-defined-literal. Hier:
3.23 operator"" - benutzerdefinierte Literale
~Der Standard erklärt alle folgenden Informationen ausführlicher in §2.14.8.~
Seit C++11 ist es nun möglich, für String-, Zeichen- und Skalar-Literale eigene Suffixe zu definieren. Ein Suffix ist ein Identifier, der unmittelbar nach einem Literal folgt*. Bekannte Suffixe aus C++03 sind bspw. f, LL oder auch u.
Wem vielleicht schon die Ähnlichkeit der Syntax mit Whitespace-Overloading aufgefallen ist, wird enttäuscht werden. Oder auch nicht, denn dieses Feature hat es auch in sich.Neue Suffixe, vorgeschlagen für C++14, sind u.a. auch s (für
std::string
),i
/il
/i_f
fürstd::complex
oderh
,min
,s
,ms
,us
undns
fürstd::chrono::duration
.Allgemeines
Genau wie bei überladenen Operatoren (nur dort mit operator-functions bzw. operator-function-templates) wird ein user-defined-literal als Aufruf des literal-operators bzw. einer Spezialisierung eines literal-operator-templates behandelt.
Findet die Implementierung nun also ein Literal der Form
684s
, kann sie zu einem Aufruf einer bestimmten Form umgewandelt werden, die ggf. von der Deklaration des literal operators oder auch literal-operator-templates abhängt.Die verschiedenen user-defined-literals
Es gibt vier verschiedene benutzerdefinierte Literale:
- user-defined-integer-literal
- user-defined-floating-literal
- user-defined-string-literal
- user-defined-character-literal
Die ersten beiden ähneln sich und werden daher unten gemeinsam behandelt.
Im Folgenden wird angenommen, dass SFX das Suffix ist, und LIT das Literal ohne Suffix.
user-defined-integer-literal / user-defined-floating-literal
Für Literale der Form
684SFX
,14.6884SFX
oder auch0.684e47SFX
.
Für Skalar-Literale gibt es verschiedene Formen. Entweder ein literal-operator ist vorhanden; in dem Fall muss er einen Parameter des entsprechenden Typs haben. Sonst muss ein literal-operator-template vorhanden sein.
Es darf vom name-lookup nicht ein raw-literal-operator und ein literal-operator-template gefunden werden (dies resultiert in einer Ambiguität).Per Parameter:
unsigned long long
(für integrale Skalare)
Hier wird ein Aufruf der Formoperator "" SFX ( LIT ULL )
gemacht.long double
(für Fließkomma-Skalare)
Hier wird ein Aufruf der Formoperator "" SFX ( LIT L )
gemacht.char const*
Ein literal-operator mit einem solchen Parameter wirdraw literal operator
genannt, da er einfach einen String mit LIT als Inhalt nimmt. Aufruf:operator "" SFX ("LIT")
.
Beispiel:
#include <iostream> std::ostream& operator"" _print( unsigned long long i ) { return std::cout << i; } long double operator"" _pi( long double i ) { return i * 3.141592635; } char const* operator"" _cut( char const* str ) { return strchr(str, '.'); } int main() { 16848_print << '\n' << 2.47_pi << '\n' << 684.486_cut << '\n'; }
Per variadischem Funktionstemplate:
Die Implementierung fordert ein variadic template, genauer ein Template mit einem non-type template parameter pack mit Elementtyp
char
und einer leeren Parameterliste. Beim Aufruf wird jedes Zeichen vor dem Suffix als Template-Argument weitergegeben, die Syntax sieht folgendermaßen aus:
operator "" SFX <’c1’, ’c2’, ... ’ck’>()
user-defined-string-literal
Für Literale der Form
P"..."SFX
**Hier wird lediglich der String (LIT) und die Länge des Strings (
len
) übergeben.
Der literal-operator muss als ersten Parameter
const wchar_t*
,
const char16_t*
,
const char32_t*
oder
const char*
haben, und als zweiten einenstd::size_t
.Aufruf:
operator "" SFX ( LIT, len )
user-defined-character-literal
Für Literale der Form
P'C'SFX
***Resultiert im Aufruf
operator "" SFX ( LIT )
. Ein entsprechender literal-operator muss den genauen Typ des Zeichens als Parameter haben.Alle möglichen Parameterlisten für literal-operators
Ein literal-operator darf keine Parameterliste außer den unten genannten haben, auch keine mit default-Argumenten.
Noch einmal zusammengefasst, alle möglichen Parameterlisten für literal-operators:**
const char*
**
Sowohl für Fließkomma- als auch für integrale Literale. Übergibt die komplette Zahl als String.**
unsigned long long int
**
Für integrale Literale. Übergibt die Zahl.**
long double
**
Für Fließkomma-Literale. Übergibt die Fließkommazahl.** `char
wchar_t
char16_t
char32_t` **
Für Zeichen-Literale; Das Zeichen wird übergeben.** `const char*, std::size_t
const wchar_t*, std::size_t
const char16_t*, std::size_t
const char32_t*, std::size_t` **
Für Strings. Nicht nullterminierter String wird als erstes Argument übergeben, Länge als zweites.* Ein Literal selbst bezeichnet natürlich das gesamte Token, mitsamt Präfixen und Suffixen. Hier ist das Literal ohne Suffix gemeint.
** Hier stellt P das Präfix dar, welches den Typ/Kodierung des Strings bestimmt;L
,u
,U
,u8
. Möglich ist auch einR
für ein raw-string-literal.
*** Hier stellt P das Präfix dar, welches den Typ/Kodierung des Zeichens bestimmt; s.o. . C ist das Zeichen selbst.Weiteres
- Suffixe die nicht mit einem Unterstrich beginnen sind reserviert. (§17.6.4.3.5)
- Die Deklarationen applikabler literal-operators oder der Templates werden per unqualified name lookup gefunden. Es wird die literal operator [template] id eingesetzt.
- Ein literal operator muss in namespace-scope deklariert sein (wobei er allerdings auch ein Freund sein kann).
- literal-operators und Spezialisierungen von literal-operator-templates sind auch nur gewöhnliche Funktionen, deren Adresse genommen werden kann, die als
inline
und (wie im Beispiel unten)constexpr
deklariert sein können usw. Es können auch template-ids für literal-operator-templates gebildet werden. Zum Beispieloperator "" SFX < Parameter >
. (Siehe auch oben)
Beispiel
Das oben verlinkte Proposal zeigt auch Möglichkeiten, Zahlen zur Compile-Zeit zu parsen und so interessante Effekte zu erzielen. Hier im Beispiel wird (das im GCC bereits als Erweiterung zu findende) Binary-Literal implementiert.
#include <iostream> #include <chrono> #include <type_traits> #include <cstddef> using namespace std; using int_type = unsigned long long; chrono::seconds operator "" _s( int_type i ) { return chrono::seconds{i}; } chrono::minutes operator "" _m( int_type i ) { return chrono::minutes{i}; } /// binary-literals: template<char ... ch> struct parse_int { template<char to_check> struct require_01 : integral_constant<bool, to_check - '0'> { static_assert( to_check == '0' or to_check == '1', "Invalid digit in bit sequence" ); }; static constexpr bool bits[]{ require_01<ch>::value... }; static constexpr size_t length = sizeof...(ch); static constexpr int_type compute_value( int cur_index = length - 1, int_type current = 0 ) { return cur_index != -1 ? compute_value( cur_index - 1, (static_cast<int_type>(bits[cur_index]) << (length - cur_index - 1)) | current ) : current; } }; template<char... ch> constexpr int_type operator "" _bin() { return parse_int<ch...>::compute_value(); } int main() { cout << "1m 20s sind " << (1_m + 10_s).count() << "s\n"; cout << "Binaeres Literal: " << 10101101_bin; }
Edit³: Oh-Oh, ich fürchte die Erklärung oben ist scheiße.
-
Noch mal überarbeitet. Damit könnte man möglicherweise einen kompletten Artikel füllen.
-
Vielen Dank!
-
Der arme kleine vollkommen unterschätze xor Operator wird immer vergessen.
-
Ach nee, der heißt in C++ ja ^. Ups.
-
Bei operator== und != sind die Parameter falsch: Es gibt nicht zwei, sondern nur einen Parameter. Der andere wird ja durch this übernommen.