Überladung von Operatoren in C++ (Teil 1)
-
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.