C++20: Designated Initializers
Designated Initialization ist eine Erweiterung von Aggregate Initialization und ermöglicht es, die Mitglieder einer Klasse direkt mithilfe ihres Namens zu initialisieren
Designated Initialization ist eine Erweiterung von Aggregate Initialization und ermöglicht es, die Mitglieder einer Klasse direkt mithilfe ihres Namens zu initialisieren
Desingated Initialization ist ein Spezialfall der Aggregate Initialization. Daher wird dieser Artikel mit Letzerer beginnen.
Aggregate Initialization
Zuerst gilt es zu klären, was Aggregate sind. Aggregate sind Arrays und Klassentypen. Ein Klassentyp ist eine class
, struct
oder union
.
Mit C++20 muss ein Klassentyp die folgenden Bedingungen erfĂĽllen:
- keine
private
- oderprotected
-nichtstatischen Mitglieder - kein benutzerdefinierter oder geerbter Konstruktor
- keine
virtual
-,private
- oderprotected
-Basisklasse - keine virtuellen Memberfunktionen
Das nächste Programm stellt Aggregate genauer vor:
// aggregateInitialization.cpp
#include <iostream>
struct Point2D{
int x;
int y;
};
class Point3D{
public:
int x;
int y;
int z;
};
int main(){
std::cout << std::endl;
Point2D point2D{1, 2}; // (1)
Point3D point3D{1, 2, 3}; // (2)
std::cout << "point2D: " << point2D.x << " " << point2D.y << std::endl;
std::cout << "point3D: " << point3D.x << " " << point3D.y << " " << point3D.z << std::endl;
std::cout << std::endl;
}
(1) und (2) initialisieren die Aggregate direkt durch Einsatz geschweifter Klammern. Die Reihenfolge der Initialisierer in den geschweiften Klammern muss der Deklarationsreihenfolge der Mitglieder entsprechen.
Basierend auf der Aggregat Initiaization in C++11 erhalten wir mit C++20 Designed Initializers. Bisher unterstützt nur der Microsoft-Compiler diese neuen Feature vollständig.
Designated Initializers
Dank Designated Initalizers ist es möglich, Mitglieder der Klasse direkt mithilfe ihres Namens zu initialisieren. Für eine union
darf nur ein Initializer verwendet werden. Wie es bereits fĂĽr Aggregate Initialization gilt, so muss die Reihenfolge der Initialisierer der Deklarationsreihenfolge der Mitglieder entsprechen:
// designatedInitializer.cpp
#include <iostream>
struct Point2D{
int x;
int y;
};
class Point3D{
public:
int x;
int y;
int z;
};
int main(){
std::cout << std::endl;
Point2D point2D{.x = 1, .y = 2}; // (1)
Point3D point3D{.x = 1, .y = 2, .z = 3}; // (2)
std::cout << "point2D: " << point2D.x << " " << point2D.y << std::endl;
std::cout << "point3D: " << point3D.x << " " << point3D.y << " " << point3D.z << std::endl;
std::cout << std::endl;
}
(1) und (2) verwenden Designated Initializers zum Initialisieren der Mitglieder. Initialisierer wie .x
und .y
werden auch gerne Designatoren (designators) genannt.
Die Mitglieder des Aggregates können bereits einen Defaultwert besitzen. Dieser wird dann verwendet, wenn der Initialisierer nicht angegeben wird. Diese Regel gilt nicht für Unions.
// designatedInitializersDefaults.cpp
#include <iostream>
class Point3D{
public:
int x;
int y = 1;
int z = 2;
};
void needPoint(Point3D p) {
std::cout << "p: " << p.x << " " << p.y << " " << p.z << std::endl;
}
int main(){
std::cout << std::endl;
Point3D point1{.x = 0, .y = 1, .z = 2}; // (1)
std::cout << "point1: " << point1.x << " " << point1.y << " " << point1.z << std::endl;
Point3D point2; // (2)
std::cout << "point2: " << point2.x << " " << point2.y << " " << point2.z << std::endl;
Point3D point3{.x = 0, .z = 20}; // (3)
std::cout << "point3: " << point3.x << " " << point3.y << " " << point3.z << std::endl;
// Point3D point4{.z = 20, .y = 1}; ERROR // (4)
needPoint({.x = 0}); // (5)
std::cout << std::endl;
}
(1) initialisiert alle Mitglieder. Das gilt aber nicht fĂĽr (2), das keinen Initialisierer fĂĽr x
besitzt. Konsequenterweise wird x
nicht initialisiert. Es ist zulässig, wenn du nur die Mitglieder initialisierst, die wie (3) oder (5) keinen Defaultwert besitzen. Der Ausdruck (4) lässt sich nicht übersetzen, denn z
und y
besitzen die falsche Reihenfolge.
Designated Initialisierer erkennen Narrowing Conversion. Dies ist die Konvertierung eines Werts mit dem einhergehenden Verlust der Datengenauigkeit:
// designatedInitializerNarrowingConversion.cpp
#include <iostream>
struct Point2D{
int x;
int y;
};
class Point3D{
public:
int x;
int y;
int z;
};
int main(){
std::cout << std::endl;
Point2D point2D{.x = 1, .y = 2.5}; // (1)
Point3D point3D{.x = 1, .y = 2, .z = 3.5f}; // (2)
std::cout << "point2D: " << point2D.x << " " << point2D.y << std::endl;
std::cout << "point3D: " << point3D.x << " " << point3D.y << " " << point3D.z << std::endl;
std::cout << std::endl;
}
(1) und (2) verursachen einen Fehler zur Compile-Zeit, da die Initialisierung .y = 2.5
und .z = 3.5f
Narrowing Conversion verursachen.
Interessanterweise verhalten sich Designated Initializers in C++ anders als in C.
Unterschiede zwischen C++ und C
C unterstützt Anwendungsfälle mit Designated Initialisers, die C++ nicht anbietet. So erlaubt C,
- die Mitglieder des Aggregates in einer anderen Reihenfolge als der Deklarationsreihenfolge zu initialisieren.
- die Mitglieder eines verschachtelten Aggregates zu initialisieren.
- Designated Initializers und reguläre Initialisierer zu vermischen.
- die Designated Initializations eines Arrays.
Das Proposal P0329R4 [1] bietet ein selbsterklärendes Beispiel für diese Anwendungsfälle an:
struct A { int x, y; };
struct B { struct A a; };
struct A a = {.y = 1, .x = 2}; // valid C, invalid C++ (out of order)
int arr[3] = {[1] = 5}; // valid C, invalid C++ (array)
struct B b = {.a.x = 0}; // valid C, invalid C++ (nested)
struct A a = {.x = 1, 2}; // valid C, invalid C++ (mixed)
Die Begründung für den Unterschied zwischen C und C++ ist Teil des Proposals: "In C++, members are destroyed in reverse construction order and the elements of an initializer list are evaluated in lexical order, so field initializers must be specified in order. Array designators conflict with ​lambda-expression​ syntax. Nested designators are seldom used." Das Dokument argumentiert weiter, dass nur eines der ausschließlich in C unterstützten Feature gerne zum Einsatz kommt: das Initialisieren der AggregateMmitglieder in einer Reihenfolge, die nicht der Deklarationsreihenfolge entspricht.
Wie geh't weiter?
Seit C++98 besitzen wir const
, mit C++11 constexpr
und mit C++20 consteval
und constinit
. In meinem nächsten Artikel werde ich einerseits auf die neuen C++20-Spezifizier consteval
und constinit
genauer eingehen und andererseits deren Unterschiede zu const
und constexpr
analysieren.
( [2])
URL dieses Artikels:
https://www.heise.de/-4835833
Links in diesem Artikel:
[1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
[2] mailto:rainer@grimm-jaud.de
Copyright © 2020 Heise Medien