Eigene Literale

C++11 bietet die Möglichkeit, eigene Literale zu definieren. Statt nur "ein String" oder 3.1415 kann man mit eigenen Suffixen komplett neue Objekte erzeugen lassen.

Video

Quellcode

#include <iostream>
#include <string>

struct ColorChar
{
    char c;
    char color;

    ColorChar()
	: c{ -1 }, color { -1 }
    {
    }

    ColorChar(char c_, char color_)
        : c { c_ }, color { color_ }
    {
    }

    bool operator==(ColorChar const &other) const
    {
	return (c == other.c) && (color == other.color);
    }
 
    bool operator<(ColorChar const &other) const
    {
        return c < other.c || (c == other.c && color < other.color);
    }
};

namespace std
{
template <> struct char_traits<ColorChar>
{
    typedef ColorChar char_type;
    typedef ColorChar int_type;
    typedef streamoff off_type;
    typedef streampos pos_type;
    typedef mbstate_t state_type;

    static bool eq(const char_type &c, const char_type &d)
    {
        return c == d;
    }

    static bool lt(const char_type &c, const char_type &d)
    {
        return c < d;
    }

    static std::size_t length(const char_type *s)
    {
        std::size_t l = 0;
        while (s->color != -1) {
            ++l;
        }
        return l;
    }
  
    static void assign(char_type &r, const char_type &c)
    {
        r.c = c.c;
        r.color = c.color;
    }

    static char_type *assign(char_type *p, std::size_t n, char_type c)
    {
        char_type *tmp = p;
        for (std::size_t counter = 0; counter < n; ++counter)
        {
            assign(*p, c);
            ++p;
        }
        return tmp;
    }

    static int compare(const char_type *p, const char_type *q, std::size_t n)
    {
        for (std::size_t counter = 0; counter < n; ++counter) {
            if (!eq(*p, *q)) {
                if (lt(*p, *q)) {
                    return -1;
                }
                else {
                    return 1;
                }
            }
        }
        return 0;
    }

    static const char_type *find(const char_type *p, std::size_t n, const char_type &c)
    {
        for (std::size_t counter = 0; counter < n; ++counter) {
            if (eq(*p, c)) {
                return p;
            }
            ++p;
        }
        return nullptr;
    }

    static char_type *move(char_type *d, const char_type *s, std::size_t n)
    {
        char_type *tmp = d;
        if (d < s) {
            for (std::size_t c = 0; c < n; ++c) {
                assign(*d, *s);
                ++d;
                ++s;
            }
        }
        else {
            d += (n-1);
            s += (n-1);
            for (std::size_t c = 0; c < n; ++c) {
                assign(*d, *s);
                --d;
                --s;
            }
        }
        return tmp;
    }
  
    static char_type *copy(char_type *d, const char_type *s, size_t n)
    {
        return move(d, s, n);
    }

    static int_type eof()
    {
        return ColorChar();
    }

    static int_type not_eof(const int_type &c)
    {
        return eq(c, eof()) ? ColorChar(0,0) : c;
    }

    static char_type to_char_type(const int_type &c)
    {
        return not_eof(c);
    }

    static int_type to_int_type(const char_type &c)
    {
        return c;
    }

    static bool eq_int_type(const int_type &x, const int_type &y)
    {
        return x == y;
    }
};

ostream &operator<<(ostream &os, basic_string<ColorChar> const &s)
{
    char color = -1;
    for (auto c : s) {
        if (c.color != color) {
            if (c.color > 7) {
                os << "\x1b[" << to_string(30-7+c.color) << ";1m";
            }
            else {
                os << "\x1b[0m\x1b[" << to_string(30+c.color) << "m";
            }
            color = c.color;
        }
        os.put(c.c);
    }
    os << "\x1b[0m";
    return os;
}

} // namespace std

std::basic_string<ColorChar> operator "" _color(const char *s, std::size_t n)
{
    std::basic_string<ColorChar> ret;
    char last_color = 7;
    for (std::size_t c = 0; c < n; ++c) {
        if (s[c] == '{') {
            if (s[c+2] == '}') {
                last_color = s[c+1]-'0';
                c += 2;
            }
            else {
                last_color = (s[c+1]-'0')*10+(s[c+2]-'0');
                c+=3;
            }
        }
        else {
            ret += ColorChar(s[c], last_color);
        }
    }
    return ret;
}

int main()
{
   std::basic_string<ColorChar> s = "He{10}ll{5}o {15}World!"_color;

   std::cout << s << std::endl;
}

Erklärung

Eigene Literale werden mit einem Suffix _name gekennzeichnet (Zeile 200). Trifft der Compiler auf einen derartigen Suffix, so sucht er nach dem passend benannten operator"" (Zeile 176) und setzt einen Aufruf für diesen eins. Abhängig von der Art des Literals (Zeichenkette, numerisch etc.) erhält der Operator die passenden Parameter. Im Beispiel hier hängt der Suffix an einem char-Array-Literal. Der Operatoraufruf bekommt daher einen Pointer auf dieses Array und dessen Länge übergeben. Der Rückgabetyp des Operators bestimmt den Typ des Literalausdrucks (deswegen könnte hier in Zeile 200 der Variablentyp auch auto lauten. Der Compiler weiß anhand des Operators, dass dieser Suffix im Typ std::basic_string<ColorChar> resultiert).

Innerhalb des Operators kann mit beliebig komplexen Operationen ein passendes Rückgabeobjekt erzeugt werden. Im Beispiel wird das Character Array nach bestimmten Markierungen durchsucht ({<farbcode>}) und diese dann in die entsprechenden Farben in den ColorChar-Objekten umgesetzt. Zusammen mit der Ausgabe auf eine ANSI-tauglichen Konsole haben wir damit alles zusammen: einen String, der Farben kodieren kann, eine Konsole, die diese Farben ausgeben kann und einen Literalsuffix, mit dem die entsprechenden Objekte im Quellcode definiert werden können (in einem realen System würden wir vermutlich eine bequem aufrufbare Funktion implementieren, welche diese Umsetzung aus dem Markup macht und diese dann nur noch aus dem Operator aufrufen. Das hätte den Vorteil, dass wir diese auch für dynamisch erzeugte Strings bequem anwenden können).