Sie sind auf Seite 1von 7

C++14

- das einfachere C++


Was ist neu und wichtig, was steckt dahinter?
Prof. Peter Sommerlad, IFS Institute for Software, HSR Rapperswil, Schweiz
Die C++ Standardisierung geschieht in einem rascheren Rhythmus als frher, nicht 5
Jahre fr einen "Bug-Fix Release" (1998-2003) und 13 Jahre fr einen neuen
Sprachstandard (1988-2011), sondern ein 3 Jahresrhythmus zwischen Korrektur und
"Feature Upgrade" des Standards ist nun geplant. Der C++11 Standard, der dieses
Jahr von einigen Compilern vollstndig implementiert wurde, wird also nchstes Jahr
2014 "korrigiert". Der Vortrag gibt einen berblick der wichtigsten Neuerungen von
C++14 und die zu erwartende weitere Entwicklung der Library anhand der
sogenannten "Technical Specifications" (TS) fr zuknftige
Bibliothekskomponenten.

Sprachdefinition
Die Angaben in diesem Artikel beziehen sich auf den zum Zeitpunkt aktuellen
Working Draft (http://isocpp.org/files/papers/N3797.pdf).

Zahlkonstanten binr und lesbarer


Mit C++14 werden binre Konstanten normiert. C++11 erlaubte zwar schon mit
Hilfe eines User-defined Literals Binrwerte anzugeben, aber das war eine Krcke.
Vor allem, weil Compiler wie der gcc schon binre Zahlen untersttzen. Deshalb
wird es ab C++14 definiert binre Zahlen geben. Analog zu hexadezimalen Zahlen
(0x) beginnt eine Binrzahl mit 0b. So entspricht 0b100 einer 4. Die blichen
Suffixes fr die Angabe des Typs (U, L, UL, LL, ULL) sind auch erlaubt.
Lange Zahlliterale, die bei binrer Schreibweise unausweichlich erscheinen und bei
anderen Formaten auch schwer lesbar sind, lassen sich nun neu mittels
Hochkommata (') trennen. Also etwa 0b1000'0001 oder 1'234'000. Das Zeichen
fr die optische Trennung wurde heftig diskutiert. Keine der Alternativen war
wirklich berzeugend. Aber es knnte Code geben, der dadurch in der Semantik
verndert wird (esoterische Prprozessor-Macro-Beispiele existieren).
Schwerwiegender knnte die Behandlung des Hochkomma bei Werkzeugen sein, die
C++ Code parsen wie Syntax-Highlighting und z.B. anhand der Hochkommata charLiterale erkennen. Auch besteht eine Gefahr fr andere Prprozessoren, die mit C++
Code "gefttert" werden. Aber alle anderen Alternativen (_ underscore, ` backtick, ,
Komma, # ) haben sich als noch problematischer herausgestellt.

Offizielles [[deprecated]] Attribut


Auch wenn ich kein groer Freund von Attributen bin, mu ich mitteilen, da es im
Standard jetzt ein definiertes Attribut gibt um veraltete Schnittstellen zu
dokumentieren. Wie alle Attribute ist [[deprecated]] fr den Compiler
"semantikfrei". Es ist aber zu erwarten, da entsprechende Warnmeldungen bei
Verwendung eines solch markierten Features (Funktion/Typ) erzeugt werden.

Weniger Einschrnkungen fr constexpr Funktionen und Literale Typen


C++11 hat nur sehr einfach strukturierte Funktionen erlaubt. Diese werden bei
entsprechender Nutzung in einem "konstanten" Kontext vom Compiler ausgewertet
und deren Ergebnis direkt eingefgt. In C++11 ist in einer constexpr Funktion nur
ein echtes Statement, und zwar "return" mglich. Allerdings ist Rekursion und
Fallunterscheidung mittels "?:" erlaubt und damit theoretisch vollstndige
Berechenbarkeit. Um neben der theoretischen Mglichkeit auch die praktikablere
Formulierung zu erlauben, sind fast alle (unsinnigen?) Einschrnkungen von
constexpr Funktionen aufgehoben. Damit der Compiler die constexpr
Funktionen in konstanten Kontexten ausrechnen kann, sind virtual, goto, trycatch verboten. Alle Parameter, lokalen Variablen und der Return Typ mssen
"Literale Typen" sein. Alle lokalen Variablen mssen initialisiert werden und drfen
nicht static oder threadlocal sein. Seiteneffekte auf Parameter oder lokale Variablen
sind erlaubt. Konstruktoren, die constexpr sind, haben quivalente Einschrnkungen
und dienen dazu eigene Literale Typen als Klassen zu definieren (Destruktor mu
trivial sein). Ferner sind die eingebauten Standardtypen, enums und structs und
Arrays von literalen Typen literale Typen. Literaler Typ bedeutet, der Compiler kann
zur Compile-Zeit damit rechnen und auch deren Konstanten-Initialisierung zur
Compile-Zeit garantieren (ROM-able Types!).
Ein weiterer Aspekt von constexpr Member-Funktionen betrifft eine inkompatible
nderung. Mit der Erweiterung der Mglichkeiten fr die Gestaltung von constexpr
Funktionen, ergibt sich nun auch die Mglichkeit eines Seiteneffekts auf das thisObjekt als impliziten Parameter einer Member-Funktion. Da andere Parameter
gendert werden knnen, ist es nicht mehr sinnvoll eine constexpr Member-Funktion
implizit als const Member-Funktion zu definieren. Sofern Sie also in C++11 schon
constexpr Member-Funktionen einsetzen, mssen Sie diese ggf. zustzlich als const
Member-Funktion markieren. Diese inkompatible nderung hat auch Auswirkungen
auf entsprechende Library Klassendefinitionen.

Constexpr Template Variablen - nicht Variadic Templates!


Variadic Templates gibt es seit C++11, also solche mit unbestimmter Anzahl von
Template Argumenten. Aber es existiert ein Problem bei der Definition von
Compile-Zeit Konstanten, die von einem Template Parameter abhngen sollen. So ist
es nicht mglich zum Beispiel Konstanten vom parametrisierten Typen, z.B.
std::complex<T> einfach zu definieren. Innerhalb einer Template-Klasse gibt es
noch das Problem, da eine static member Variable (die const sein kann) neben
der Deklaration in der Klasse auch noch separat auerhalb der Klasse definiert sein
mu, wenn irgendwo ihre Adresse benutzt wird. In der Standardbibliothek wird
dieses Problem teilweise durch entsprechende static constexpr
Memberfunktionen bei std::numeric_limits<T> abgeschwcht. Aber es wre
eigentlich schn, std::numeric_limits<int>::max zu schreiben, statt
std::numeric_limits<int>::max(). Mit den erweiterten Mglichkeiten fr
literale Typen ergeben sich noch mehr Anwendungsmglichkeiten.
Template Variablen (eigentlich Konstanten!) erlauben es solche Typabhngigen
constexpr Variablen zu definieren (auch ausserhalb von Klassen).
template<typename T> constexpr T pi =
T(3.1415926535897932385);

Versatilitt mit auto fr Return-Typ Deduktion und generische Lambdas


Fr gengend einfache Lambda-Funktionen kann man in C++11 schon den Return
Typ weglassen. Bei Funktionen kann mit auto als Return-Typ einen Lambda-artigen
"trailing return type" nach der Parameterliste angeben (auto foo()->int;). Das
letztere ist vor allem in generischem Code sinnvoll, erfordert dann aber oft die
Repetition einer Expression, die nochmals im Funktionsbody vorkommt. Diese
Repetition ist mhsam. Analog zur Erweiterung bei constexpr Funktionen sind in
C++14 auch die Mglichkeiten hinsichtlich der Return-Typ-Deduktion erweitert
worden. So kann neu der "trailing return type" auch bei komplexeren Lambda
Ausdrcken weggelassen werden, also solchen, die mehrere Statements und mehr als
ein return Statement beinhalten. Allerdings mssen diese return Statements
Werte von identischem Typ zurck geben. Diese Regel der konsistenten return
Statements gilt nun auch fr alle Funktionen, die mit auto als Return-Typ
gekennzeichnet sind. Der Compiler ermittelt den eindeutigen Rckgabetypen.
Hilfreich ist dies vor allem bei Template Funktionen, oder solchen, die ein Lambda
zurck geben.
Anstelle von auto fr die Typdeduktion gibt es nun auch den Ausdruck
decltype(auto). Im Gegensatz zu auto fhrt decltype(auto) dazu, da bei
der Typdeduktion von Referenzen auch die Referenzeigenschaft erhalten bleibt.
auto hingegen ermittelt den zugrundeliegenden Typ, auto&& immer eine Referenz.
delctype(auto) kann auch fr die Return-Typ Deduktion bei Funktionen oder als
"trailing-return-type" in Lambda Ausdrcken genutzt werden.
Typdeduktion kann neu auch fr die Parameter eines Lambdas genutzt werden, so
wie fr Parameter von Template-Funktionen. Allerdings mu man das Lambda nicht
mit einem template Schlsselwort versehen, sondern die Parameter mit dem "Typ"
auto deklarieren.
auto const square=[](auto x) { return x*x; };

Dieser Lambda Ausdruck entspricht nun einer Template-Funktion mit einem


Typename Template Parameter. Da das Lambda keine Capture besitzt, kann das
Lambda sogar als Funktionszeiger reprsentiert werden und mu im Gegensatz zu
berladenen Funktionen nicht einmal per static_cast der passende Overload
ausgewhlt werden, z.B.:
double (*sq)(double)=square;
int (*sqi)(int)=square;
square(2f);
sq(2.0);
sqi(2);

Im Gegensatz zu C++11 knnen im Rahmen der Capture eines Lambda Ausdrucks


auch neue (Member-)Variablen im Lambda definiert werden. Das erlaubt nun auch
die Capture per Value von Move-only Typen wie std::unique_ptr, was vorher
nur per Referenz-Capture mglich war, mit dem Problem der mglicherweise
"dangling Reference".
Sollten mehrere auto Parameter bei einem Lambda angegeben werden, werden die
jeweiligen Typen unabhngig voneinander deduziert. Auch Variadic-Lambdas sind
mglich, also die Verwendung von [](auto...params){}. Dies alles auch mit

universellen Referenzen (auto&&) also [](auto&&...params){}. Vor allem in


generischem Code fhrt das zu deutlich flexiblerer Nutzung von Lambda
Ausdrcken und der Ermglichung von "perfect forwarding" auch bei Lambdas.

Vergessen: ::operator delete(void *, size_t)


Mit C++11 fhrte die Mglichkeit Einzug Klassenspezifisch operator delete
mit einem zustzlichen size_t Parameter zu versehen, den der Compiler mit der
beim allozieren mit new verwendete Objektgre mitgibt. Dies erlaubt es dem
unterliegenden Allokationsmechanismus effizienter den Speicher wieder
freizugeben. Fr den globalen operator delete gab es diesen Overload nicht. Er
soll mit C++14 auch fr den globalen operator delete eingefhrt werden und
kann bei Allokatoren mit Pools nach Objektgre die Effizienz (Platz und Zeit) bei
kleinen Objekten deutlich erhhen.
Nicht geschafft nach C++14, bzw. in Chicago wieder herausgewhlt wurden
dynamische Stack-basierte Arrays (nicht identisch zu C's Variable-Length Arrays
VLAs). Der Grund ist, da unklar war wie diese sich in das strenge C++ Typsystem
sinnvoll einbetten lassen. Deshalb wurde auch die Library-Klasse dynarray wieder
entfernt. Beide Teile werden voraussichtlich zuerst in einer "Technical Specification"
oder TS verffentlicht um Erfahrung mit ihrer Implementierung zu sammeln. Zu
beachten ist, da sich die Inhalte in einem TS bis zur Standardisierung noch ndern
knnen, wenn die Erfahrungswerte zeigen, da das ursprngliche Design Schwchen
hat. Bis dahin lohnt es sich also in produktivem Code auf std::vector zu setzen
und auf den mglichen Performance-Gewinn zu verzichten.

Library
Auch bei der Library wurden einige weitere Dinge im Rahmen des letzten Meetings
wieder herausgenommen. Neben dem erwhnten dynarray gehrt dazu auch
optional<T>. Grund war vor allem, da bestimmte Details des Designs nicht
ausgiebig genug erprobt waren und da dafr eine einheitliche Strategie auch wegen
der neuen Sprachmglichkeiten fr die Library bislang fehlt. Diese
herausgenommenen Teile und weitere Aspekte wie eine Dateisystem-Schnittstelle
werden in weiteren TS in 2014 verffentlicht, sind aber wie gesagt damit noch nicht
endgltig normiert. Anpassungen bei der Verwendung sind zumindest vom
namespace her (std::experimental) zu erwarten.

Neues fr den alltglichen Gebrauch in der Standard-Library


Fr die tgliche Arbeit am wichtigsten halte ich folgende Ergnzungen:
make_unique() zur Erzeugung von std::uniqe_ptr analog zu make_shared()
quoted() Manipulator zum symmetrischen I/O von Strings.
exchange(var,new_value) zum (nicht-atomaren) Ersetzen eines Variableninhalts
und ermitteln des alten Inhalts. Dies kann als Ersatz fr den obsoleten
operator++(bool,int) genutzt werden.
Heterogene Vergleichsfunktoren fr Assoziative Container. Damit kann z.B. eine
Person in einem std::set anhand ihres Namens (string) gefunden werden, ohne
dafr ein dummy Personenobjekt mit dem Namen zu erzeugen. Voraussetzung dafr

ist die Existenz des operator-Overloads von operator<(Person,std::string)


bzw. mit vertauschten Parametertypen.
Der letzte Aspekt wird mit Hilfe generischer Spezialisierungen der
Standardfunktoren, wie std::less<> erreicht. Damit kann man nun durch
Anwendung der Default-Spezialisierung fr void beliebige Typen mit diesen
Funktoren kombinieren. Z.B. std::plus<>("hallo","Peter"s); nutzt
operator+(char const *, std::string).
Im letzten Beispiel sehen wir auch einen weiteren Mechanismus, der in die
Standardbibliothek durch mich eingefhrt wurde: Normierte User-defined Literal
Suffixes. In den Namespaces std::literals, bzw. std::literals::
string_literals ist das Suffix s fr string Literale definiert und sorgt dafr, da
der entsprechende Wert als Instanz von std::string generiert wird. Fr
std::chrono::duration existieren entsprechende Suffixes fr Zahlen, die
Stunden(h), Minuten(min), Sekunden(s), Millisekunden(ms) usw. definieren. Der
imaginre Anteil (Faktor von squrt(-1)) einer komplexen Zahl lsst sich mittels
des Suffix i (bzw. if und il) angeben auto x=1+2i;
Ein Hinweis fr Nutzer von std::future und async(): Es scheint so, da nicht
allen Benutzern von async() klar ist, da sie das Ergebnis des Aufrufs nicht
einfach ignorieren drfen, da bei paralleler Ausfhrung der Destruktor der
std::future eventuell auf das Beenden des gestarteten Thread warten mu, wenn
nicht vorher das Ergebnis der std::future ermittelt wurde. Dieser Sachverhalt ist
jetzt deutlicher im Standard kommuniziert und es wird verboten, da std::future
Objekte aus anderen Quellen als std::async() im Destruktor blockieren.

Library Ergnzungen fr die generische Programmierung


Mit dem nchsten Standard werden viele Vereinfachungen auf der Library-Ebene
eingefhrt, die die generische Programmierung mit Templates vereinfachen oder es
zumindest erlauben wesentlich krzeren Code zu schreiben.
Krzere Meta-Funktionen
Bisher hatten die Meta-Funktionen, also solche, die aus einem Typ-Parameter einen
anderen Typ erzeugen, einen typename Member ::type. Um sie zu verwenden,
war vor allem bei Verschachtelungen eine groe Menge an typename Keywords
notwendig. Mit C++14 wird fr jedes Meta-Funktion-Klassentemplate ein Template
Alias eingefhrt, der wie die Meta-Funktion mit _t Suffix heit und der den Member
::type referenziert, z.B.
template <typename T>using decay_t= typename decay<T>::type;

Eine weitere Erleichterung betrifft die Klasse integral_constant, die benutzt wird um
verschiedene Eigenschaften von Typen abzubilden - i.d.R. ber die Unterklassen
true_type und false_type. Bisher mute der ::value Member mit der
entsprechenden Konstanten genutzt werden. Neu geht das auch, indem man die
Klasse instanziiert und mit dem operator() auf den Wert zugreift.
Ein Vorher-Nachher Beispiel verdeutlicht die Verkrzungsmglichkeiten:

template< typename T > using reference_t =


typename conditional<is_reference<T>::value,
T, typename add_lvalue_reference<T>::type>::type;
wird mit C++14 zu:
template< typename T > using reference_t =
conditional_t<is_reference<T>{}(),
T,add_lvalue_reference_t<T>>;
Tuple Nutzung
Obwohl noch nicht alle "netten" Features, die einmal zur Diskussion standen fr die
Nutzung von std::tuple und variadic Templates, es in C++14 geschafft haben,
mchte ich die Klasse integer_sequence resp. index_sequence zeigen. Sie
erlaubt es kompakt ein variadic Template mit einer Sequenz von Integern zu
versorgen, die z.B. genutzt werden knnen um mittels Pack-Expansion auf alle
Elemente eines Tupels zu verweisen. Hier ein Beispiel der Anwendung eine
apply() Implementierung, die eine Funktion mit den Elementen eines Tupels
aufruft:
template <typename F, typename Tuple, size_t... I>
decltype(auto)
apply_impl(F&& f, Tuple&& t, index_sequence<I...>){
return forward<F>(f)(get<I>(forward<Tuple>(t))...);
}
template <typename F, typename Tuple>
decltype(auto)
apply(F&& f, Tuple&& t) {
return apply_impl(forward<F>(f), forward<Tuple>(t),
make_index_sequence<tuple_size<decay_t<Tuple>>::value>{});
}

Eine weitere nette Erleichterung der Nutzung von std::tuple ist, da man nun mittels
get<Typ>(tuple) auf ein (eindeutiges) Tuple-Element per Typ zugreifen kann,
statt nur wie bisher ber die Position.
Convenience Iteratoren fr einfache Generische Programmierung
Neben den vom Range-for Loop genutzten Funktionen std::begin/std::end, die bei
Containern (und anderen Ranges) die passenden Iteratoren liefern, gibt es nun auch
die Varianten fr die sonstigen verfgbaren Iteratoren: cbegin/cend,
crbegin/crend und rbegin/rend fr konstante und rckwrts durchlaufene
Ranges.
Eine weitere Ergnzung erlaubt nun, da "Forward"-Iteratoren default-konstruierbar
sein mssen. Dies erlaubt die einfachere Gestaltung mancher Algorithmen, weil dort
Hilfsvariablen einfacher definiert werden knnen.

Ausblick
Es ist abzusehen, da erste C++14 kompatible Compiler und Libraries sptestens in
2014 erscheinen. Die Entwicklungsversionen von Clang und libc++ bieten heute
schon fast alles an Features, die oben beschrieben wurden. Die GNU Compiler
Collection ist dicht auf den Fersen. Leider haben einige der groen Hersteller mit

Compilern mit lteren Wurzeln noch deutliche Probleme mit C++11, wobei manche
vielleicht statt einem zuerst kompletten C++11 Release eventuell gleich auf den
C++14 Zug aufspringen.
Erwarten Sie weiterhin Neues aus der C++ Welt. Auf jeden Fall ist C++ mit C++11
und C++14 viel leichter und sicherer nutzbar geworden und auch konzeptuell
schwierige Aspekte wie die Meta-Programmierung erschlieen sich nun in einer
syntaktisch beherrschbareren Variante. Fr die Zukunft sollten Sie weitere
Verbesserungen erwarten, wie zum Beispiel Compile-Time Meta-Information auf
Sprachebene oder die Normierung der vielen Library Komponenten, die in der
Pipeline der Technical Specifications stecken. Schauen Sie regelmig auf
isocpp.org nach um am Ball zu bleiben.
Fr die Embedded Umgebungen erschlieen sich mit C++11/14 vor allem durch
constexpr und damit literale Typen vllig neue Welten in der Typsicheren und
effizienten maschinennahen Kodierung. Lernen Sie diese und wenden Sie sie an!