C++17: Kleinvieh macht auch Mist

Seit Juni ist C++17 Feature-komplett. Das ist ein guter Anlass, die Neuerungen zu betrachten. Dieser Artikel widmet sich zunächst den Sprachmitteln.

Sprachen  –  34 Kommentare
C++17: Kleinvieh macht auch Mist

Als die Ankündigung für C++17 als das nächste Major Release erfolgte, waren die Erwartungen ähnlich hoch wie bei C++11. Es zeigte sich jedoch, dass die Zeit schlichtweg zu kurz war. C++11 hatte allerdings seit 1998 dreizehn beziehungsweise seit 2003 acht Jahre Zeit zu reifen, C++17 dagegen nur sechs.

Auch bei C++ zeigt sich der Fluch großer Software, die rückwärtskompatibel sein muss und von einer Community gepflegt wird: Die Zeit, bis ein Feature einen Reifegrad hat, mit dem die deutliche Mehrheit der standardisierenden Personen und Organisationen leben kann, ist länger als anfangs erwartet.

Für etliche lang ersehnte (und zum Teil avisierte) neue Features konnten die Macher keinen Konsens erzielen. Concepts, Modules, Reflection, Ranges, Contracts, Default-Vergleichsoperatoren, das Überladen vom Punkt-Operator, Co-Routinen und Transactional Memory haben es nicht geschafft. Teilweise gibt es aber Beta-Standards (sogenannte Technical Specifications), über die Entwickler sie ausprobieren können.

Immerhin gibt es wichtige Bibliotheken wie das Dreigestirn variant, optional, any, eine Filesystem-Bibliothek und die Unterstützung parallel laufender STL-Algorithmen. Diese Themen wird der nächste Teil des zweiteiligen Artikels behandeln.

Der erste Teil konzentriert sich dagegen auf die Neuerungen bei den Sprachmitteln. In Summe findet sich nichts Revolutionäres, aber eine signifikante Anzahl sinnvoller Verbesserungen, die das Programmieren komfortabler machen.

Unter dem Begriff "Structured Bindings" erlaubt C++17, mehrere Objekte gleichzeitig mit den Elements eines Array, einer Struktur oder eines Tuples zu initialisieren. Das macht vor allem die Verwendung mehrerer Rückgabewerte deutlich einfacher.

Das folgende Beispiel zeigt die Initialisierung der zwei int-Variablen x und y mit den Elementen aus einem zurückgelieferten Array:

int[2] f();
auto [ x, y ] = f();

Die Initialisierung von drei Variablen a, b und c mit den drei Elementen eines Tuples, bei denen die Variablen die entsprechenden Datentypen der Tuple-Elemente übernehmen, sieht folgendermaßen aus:

tuple<T1,T2,T3> g();
auto [a,b,c] = g();

Der folgende Code zeigt die Initialisierung zweier Variablen u und v mit den Komponenten einer zurückgelieferten Struktur:

struct MyStruct {
int x;
double y;
};
MyStruct h();
auto [u,v] = h();

Analog zu den Datentypen der MyStruct-Komponenten bekommt u den Typ int und v den Typ double.

Entwickler können dieses Feature nutzen, um mit auto und den üblichen Modifizierungen auf die Weise bequem über alle Schlüssel-Werte-Paare einer Map zu iterieren:

std::map<string, double> mymap;
...
for (const auto& [key,val] : mymap) {
std::cout << key << ": " << val << std::endl;
}

Der Sprachstandard bringt neue Kontrollstrukturen bei den Fallunterscheidungen: Sowohl if als auch switch dürfen jetzt optional eine Initialisierung im Anweisungskopf haben. Das ist vor allem hilfreich, wenn neben dem Test einer Bedingung dessen Initialisierung und Weiterverarbeitung erforderlich ist, wie dieses Beispiel zeigt:

if (status s = check(); s != SUCCESS) {
return s;
}

Vor C++17 sah der Code dazu folgendermaßen aus:

{
status s = check();
if (s != SUCCESS) {
return s;
}
}

Lokale Locks sind damit ebenfalls einfacher. Bisher sah der Code folgendermaßen aus:

{
std::lock_guard<std::mutex> lg(mv);
if (v.empty()) {
v.push_back(initVal);
}
}

Jetzt können die geschweiften Klammern außen wegfallen, da die Initialisierung des Lock im Scope von if erfolgt:

if (std::lock_guard<std::mutex> lg(mv);
v.empty() {
v.push_back(initVal);
}

Wieder hat sich Etliches bei den Berechnungen zur Compile-Zeit getan – für Templates und constexpr.