Ü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ür std::complex oder h , min , s , ms , us und ns für std::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 auch 0.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 Form operator "" SFX ( LIT ULL ) gemacht.
    • long double (für Fließkomma-Skalare)
      Hier wird ein Aufruf der Form operator "" SFX ( LIT L ) gemacht.
    • char const*
      Ein literal-operator mit einem solchen Parameter wird raw 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 cic_i 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 einen std::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 ein R 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 Beispiel operator "" 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.


Anmelden zum Antworten