I/O-Manipulatoren mit Zustand

Wenn ein I/O-Manipulator einen Zustand haben muss, dann sieht die Implementierung etwas anders aus, als für ein einfaches std::endl.

Video

Quellcode

#include <iostream>
#include <iomanip>

using namespace std;

struct my_width
{
    int width;
    
    my_width(int w)
     : width(w)
    {
    }
};

std::ostream &operator<<(std::ostream &os, my_width const &ws)
{
    os.width(ws.width);
    return os;
}

my_width setmw(int width)
{
    return my_width(width);
}

int main()
{
    cout << setfill('s') << setw(10) << 15 << std::endl;
    cout << setfill('m') << setmw(10) << 25 << std::endl; 
    cout << setfill('u') << my_width(10) << 35 << std::endl; // eigentlich unzulässig
}

Erklärung

Der Standard lässt dem Implementierer von std::setw relativ große Freiheiten. Die Funktion ist spezifiziert als

unspecified setw(int width);

Über den Rückgabetyp wird (vereinfacht) ausgesagt, dass er so beschaffen sein muss, dass der Ausdruck os << setw(x) die gleiche Wirkung haben muss, wie os.width(x) (die Details sind etwas komplizierter, aber so in etwa kann man das interpretieren). Wie der Compiler das genau realisiert ist nicht ausgeführt.

Eine mögliche Implementierung nutzt einen speziellen Output-Operator, der die gesamte Arbeit macht (Zeilen 16 bis 20). Dieser wird getriggert von einem Datentyp, der nur als Wrapper für die Feldbreite dient: my_width dient nur als Markierung zur Auswahl des korrekten Ausgabeoperators. Die Struktur selbst implementiert kein Verhalten. Der Manipulator ist nun als Funktion mit dem Rückgabetyp my_width implementiert und liefert ein Objekt mit der passenden Feldbreite zurück.

Rein vom Aufrufsyntax her wäre Zeile 31 auch möglich. Statt des Aufrufs von setmw wird direkt der Konstruktor der Struktur aufgerufen. Allerdings verstehe ich die entsprechende Stelle im Standard so, dass der Manipulator eine Funktion sein muss. Damit wäre der direkte Konstruktoraufruf aus meiner Sicht unzulässig.