Nachdem ich das große Bild zur neuen C++ Kernsprache bereits in dem Artikel "C++17: Was gibt's Neues in der Kernsprache?" präsentiert habe, gibt es heute weitere Details. In den Details geht es hauptsächlich um inline Variablen, Templates, automatische Typableitung mit auto und Attribute.
Hier ist das große Bild zu C++17 nochmals.
Aber heute geht es um die nicht so bekannten Features.
Inline Variablen
Dank inline Variablen existiert der entscheidende Grund nicht mehr, C++ Code als header-only Bibliothek zu verpacken. Du kannst nun globale Variablen und statische Variablen als inline erklären. Dieselben Regeln, die für inline Funktionen gelten, gelten nun auch für inline Variablen.
Das bedeutet:
- Es kann mehr als eine Definition einer inline Variable geben.
- Die Definition einer inline Variable muss in jeder Übersetzungseinheit zur Verfügung stehen, in der sie verwendet wird.
- Eine globale inline Variable (non-static inline Variable) muss in jeder Übersetzungseinheit als inline deklariert werden und besitzt in jeder Übersetzungseinheit dieselbe Adresse.
Nochmals der große Vorteil von inline Variablen. Du kannst deine Variablen direkt in der Headerdatei verwenden und sie mehrmals einbinden.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// widget.h class Widget{ public: Widget() = default; Widget(int w): width(w), height(getHeight(w)){} Widget(int w, int h): width(w), height(h){} private: int getHeight(int w){ return w*3/4; } static inline int width= 640; static inline int height= 480; static inline bool frame= false; static inline bool visible= true; ... }; inline Widget wVGA; |
auto kann automatisch den Typ seiner Variable von seinem Initialisierer ableiten. Aber die Geschichte mit auto geht weiter. Dank auto ist es möglich, das Template-Parameter von Funktions-Templates und Konstruktoren automatischen von ihren Argumenten abgeleitet und das Nicht-Typ Template-Parameter automatisch von ihren Template-Argumenten abgeleitet werden können. Bist du irritiert über den letzten Teil meines Satzes? Das ist gut. Genau darüber schreibe ich im nächsten Kapitel.
Automatische Typableitung von Nicht-Typ Template-Parametern
Aber zuerst einmal. Was sind Nicht-Type Template-Parameter. Das sind nullptr-, Ganzzahl-, lvalue Referenz-, Zeiger- und Aufzählungstypen. In meinen Artikel werde ich mich fast nur auf Ganzzahltypen zurückziehen.
Nach soviel Theorie, geht es mit einem Beispiel los.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
template <auto N> class MyClass{ ... }; template <int N> class MyClass<N> { ... }; MyClass<'x'> myClass2; // Primary template for char MyClass<2017> myClass1; // Partial specialisation for int |
Da ich auto (Zeile 1) in der Template-Signatur verwende, ist N ein Nicht-Typ Template-Parameter. Der Compiler kann diesen automatisch bestimmen. Die teilweise Spezialisierung für int in Zeile 6 und 7 wird von C++17 auch unterstützt. Die Template-Instanziierung in Zeile 12 wird das primäre Template (Zeile 1-4) und die folgende Template-Instanziierung die teilweise Spezialisierung für int verwenden.
Die bekannten Typmodifizierer können eingesetzt werden um den Typ des Nicht-Typ Template-Parameters einzuschränken. Daher musst du keine teilweise Spezialisierung anwenden.
template <const auto* p>
struct S;
In diesem Fall muss p ein Zeiger auf const sein.
Die automatische Typableitung für Nicht-Typ Template-Parameter funktioniert auch für Variadic Templates.
1 2 3 4 5 6 7 8 9 |
template <auto... ns> class VariadicTemplate{ ... }; template <auto n1, decltype(n1)... ns> class TypedVariadicTemplate{ ... }; |
Daher kann das VariadicTemplate in Zeile 1-4 eine beliebige Anzahl von Nicht-Typ Template-Parameter ableiten. TypeVariadicTemplate bestimmt nur den ersten Parameter. Die verbleibenden Template-Parameter besitzen denselben Typ.
Die Regeln für auto in Kombination mit {}-Initialisierung ändern sich mit C++17.
auto in Kombination mit {}-Initialisierung
Falls du auto in Kombination mit {}-Initialisierung verwendest, erhälst du eine std::initializer_list.
auto initA{1}; // std::initializer_list<int> auto initB= {2}; // std::initializer_list<int> auto initC{1, 2}; // std::initializer_list<int> auto initD= {1, 2}; // std::initializer_list<int>
Das ist eine einfach Regel. Einfach zu behalten und einfach zu erklären. Leider ändert sich das mit C++17 und das meiner Ansicht nach nicht zum besseren.
auto initA{1}; // int auto initB= {2}; // std::initializer_list<int> auto initC{1, 2}; // error, no single element auto initD= {1, 2}; // std::initializer_list<int>
Nun werden die Regeln kompliziert. Zuweisung mit {} gibt immer eine std::initializer_list. Copy-Zuweisungen funktioniert nur für einen einzelnen Wert.
Nun zu einem kleinen aber feinen Feature.
Verschachtelte Namensräume
Mit C++17 ist es recht einfach, verschachtelte Namensräume zu definieren.
Anstelle von
namespace A { namespace B { namespace C { ... } } }
kannst du die einfachere Schreibweise verwenden:
namespace A::B::C {
...
}
C++17 erhält die neuen Attribute [[fallthrough]], [[nodiscard]] und [[maybe_unused]].
Die drei neuen Attribute fallthrough, nodiscard und maybe_unused
All drei beschäftigen sich mit Compiler Warnungen. Die Beispiele sind von cppreference.com.
fallthrough
[[fallthrough]] kann in einer switch Anweisung verwendet werden. Es muss auf einer Zeile stehen, direkt vor einem case Bezeichner. Das Attribut zeigt dem Compiler an, dass ein Durchrutschen durch den Anweisungsblock beabsichtigt ist und daher keine Compiler Warnung dies anzeigen soll.
Hier ist ein kleines Beispiel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void f(int n) { void g(), h(), i(); switch (n) { case 1: case 2: g(); [[fallthrough]]; case 3: // no warning on fallthrough h(); case 4: // compiler may warn on fallthrough i(); [[fallthrough]]; // illformed, not before a case label } } |
Das [[fallthrough]] Attribut in Zeile 7 unterdrückt die Compiler Warnung. Das gilt nicht für die Zeile 10. Die Zeile 12 hingegen ist syntaktisch falsch, da kein case Bezeichner folgt.
[[nodiscard]] kann in Funktions-, Aufzähler- und Klassendeklaration verwendet werden. Falls du den Rückgabewert einer als nodiscard deklarierten Funktion ignorierst, soll der Compiler eine Warnung ausgeben. Dasselbe gilt für eine Funktion, die eine Aufzählung oder eine Klasse zurückgibt. Dies gilt aber nicht, falls eine Konvertierung nach void angewandt wird.
nodiscard
Daher soll Zeile 5 eine Warnung ausgeben. Zeile 10 soll keine Warnung ausgeben, da die Funktion foo eine Referenz zurückgibt.
1 2 3 4 5 6 7 8 9 10 11 |
struct [[nodiscard]] error_info { }; error_info enable_missile_safety_mode(); void launch_missiles(); void test_missiles() { enable_missile_safety_mode(); // compiler may warn on discarding a nodiscard value launch_missiles(); } error_info& foo(); void f1() { foo(); // nodiscard type is not returned by value, no warning } |
maybe_unused
[[maybe_unused]] kann für die Deklaration einer Klasse, eines typedef, einer Variable, eines Nicht-statischen Mitglied einer Klasse, einer Funktion, einer Aufzählung oder eines Aufzählers verwendet werden. Dank maybe_unused unterdrückt seine Warnung zu einer nicht verwendeten Entität.
1 2 3 4 5 6 |
void f([[maybe_unused]] bool thing1, [[maybe_unused]] bool thing2) { [[maybe_unused]] bool b = thing1; assert(b); // in release mode, assert is compiled out } |
Im Release-Modus wird Zeile 5 nicht kompiliert. Der Compiler soll aber keine Warnung ausgeben, da b als maybe_unused erklärt wird. Das gleiche gilt für die Variable thing2.
Wie geht's weiter?
Nach meiner Einführung in die C++17 Kernsprache(C++17: Was gibt's Neues in der Kernsprache?), lieferte ich in diesem Artikel mehr Details. Das gleiche gilt für meinen nächsten Artikel. I werde in meinem nächsten Artikel mehr Details zu den C++17 Bibliothek liefern. Daher, falls dich C++17: Was gibt´s Neues in der C++ Bibliothek neugierig gemacht hat.
Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library". Hole dir dein E-Book. Unterstütze meinen Blog.
Weiterlesen...