Eigene Zeichen ausgeben

Wer versucht hat, den std::basic_string<ColorChar> auszugeben, dem dürfte aufgefallen sein, dass die Standardbibliothek damit gar nicht so glücklich ist. Das soll sich ändern.

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

int main()
{
   std::basic_string<ColorChar> s { ColorChar('h',15), ColorChar('e', 5), 
                                    ColorChar('l', 10), ColorChar('l', 10), 
                                    ColorChar('o', 2) };

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

Erklärung

Viel hat sich gegenüber letzter Woche nicht geändert: es ist nur std::ostream &operator<<(std::ostream &os, std::basic_string<ColorChar> const &s) hinzugekommen. Dieser soll unseren farbig kodierten String ausgeben. Der freie Operator ist die zweite Variante, wie man in C++ Operatorüberladung betreiben kann: Entweder wird der Operator als Member des auf der linken Seite stehenden Typs deklariert werden (funktioniert im Beispiel nicht, weil wir die Klasse std::ostream nicht mehr nachträglich ändern können) oder er kann als freier Operator mit zwei Parametern definiert werden. Dabei ist der erste Parameter die linke Seite des Aufrufs und der zweite die rechte Seite.

Um die Farben zu erzeugen bedienen wir uns der ANSI-Farbcodes. Viele Konsolen interpretieren die Zeichenfolge ESC[ als Escapesequenz, die für die folgenden Zeichen die Interpretation ändert. Statt das einfach auszugeben, werden die folgenden Zeichen als Farbcode interpretiert, der für die folgenden Zeichen die Farbe umschaltet. ACHTUNG: das Vorgehen ist nicht portabel oder Bestandteil von C++. Das ist eine Eigenschaft der Konsole, auf der die Daten ausgegeben werden!

Die Sequenz ist oben im Quellcode als "\x1b[" kodiert. \x1b ist das Zeichen (hexadezimal) 1B: ESC (laut ASCII-Tabelle). Die Vordergrundfarbe kann dann nach einer vordefinierten Tabelle gesetzt werden (http://en.wikipedia.org/wiki/ANSI_escape_code#Colors), indem zum Farbindex 30 addiert werden. Gibt man diese Sequenz aus, so merkt sich die Konsole das und schaltet auf die neue Farbe um. Alle Zeichen, die danach ausgegeben werden,