Container mit fixer Grösse auszupacken kann in C++ anstrengend sein und man muss mit
std :: get
and / or
std :: tie
herumbasteln. Doch diese Zeiten sind mit C++17 vorbei – Dank den neu eingeführten structured bindings. Das Auspacken von Datenstrukturen mit fixer Grösse war noch nie so einfach und bequem.
Jede Datenstruktur, deren Grösse zur Compiletime bekannt ist, kann nun mit minimalem Syntax extrahiert werden. Und das funktionier sogar mit structs und membern von Klassen.
[sourcecode lang = “cpp“] auto tuple = std :: make_tuple (1, 'a', 2.3); std :: array <int, 3> a {1, 2, 3}; // unpack the tuple into individual variables declared above const auto [i, c, d] = tuple; // same with an array auto [x, y, z] = a; [/ sourcecode]
Structured Bindings müssen immer mit auto inklusive allen const-dekorationen und by-refernce oder by-value Spezifikationen deklariert werden. Dies verhindert das explizite Casting der Daten in andere als die zur Compilezeit bekannten Datentypen. Da explizites casting generell mit Vorsicht genossen werden sollte, ist diese Limitation eher hilfreich beim Schreiben von typisiertem Code als dass sie eine Behinderung ist. Alle Bindings haben die selbe const-heit und sind alle entweder kopiert oder als Referenz verfügbar, was aber auch heisst das nur teilweise veränderbarer Zugriff auf die Datenstruktur nicht möglich ist. Alles oder nichts heisst hier die Regel.
Das Auspacken von Klassen und Structs ist wie erwähnt möglich, hat aber einige mögliche Fallstricke.
[sourcecode lang = “cpp“] struct MyStruct { int x; char y; float z; }; MyStruct s; // access by reference auto & [x, y, z] = s; // access by move auto && [xx, yy, zz] = s; class cls { public: int m; float n; }; auto [m, n] = cls (); [/ sourcecode]
Soweit funktioniert alles wie erwartet, aber da das Auspacken von der Reihenfolge der Deklaration abhängt ist hier Vorsicht geboten. Solange diese Reihenfolge streng kontrolliert werden kann ist das ganze relativ problemlos, aber da members von structs und Klassen bereits einen Namen haben werden sie vom Programmierer of nicht „stabil positioniert“. Die Erfahrung zeigt zum Beispiel, dass während refactorings Klassenmember oft semantisch neu gruppiert und geordnet werden, was fatal sein kann falls diese in strukturierten Bindings verwendet werden.
Eine viel bessere Herangehensweise für Klassen und structs ist für diese Klassen tuple-ähnliches verhalten zu implementieren, was mittels Spezialisierung von std::tuple_size, std::tuple_element und get gar nicht so schwer ist. Das funktioniert übrigens wunderbar zusammen mit dem constexpr if, einem weiteren Feature aus C++17. Die Spezialisierung entkoppelt die Reihenfolge der Deklaration von der tuple-Semantik und erlaubt es auch den return-typ von Parametern zu ändern oder zusätzliche Informationen an einer fixen Position zurückzugeben.
[sourcecode lang = “cpp“] // this illustrates how to make a class support structured bindings class Bindable { public: template decltype (auto) get () const { // note the changing of the type from std :: string to const char * for the 2nd parameter // of returning a reference to the vector if constexpr (N == 0) return x; else if constexpr (N == 1) return msg.c_str (); else if constexpr (N == 2) return (v); // parentheses make a reference out of this else if constexpr (N == 3) return v.size (); // return some meta information } private: int x {123}; std :: string msg {"ABC"}; std :: vector v {1,2,3}; }; /// template specialization for class Bindable namespace std { template <> struct tuple_size: std :: integral_constant <std :: size_t, 4> {}; template struct tuple_element <N, Bindable> { using type = decltype (std :: declval (). get ()); }; } [/ sourcecode]
Eine Randnotiz zu den Structured Bindings ist, dass sie keine teilweise Extraktion untersützen, wie das mit
std :: tie
und
std::ignore
möglich war. Damit ist man im Moment dazu gezwungen wegwerf-Variablen zu erstellen, wenn man nur einen Teil eines tuples verwenden will. Aber auch das sollte durch die seit C++17 garantierte copy elision effektfrei sein, sobald irgendeine compiletime Optimierung eingeschaltet ist.
Abschliessend ist zu sagen, dass die Structured Bindings ein schöner Weg sind schlanken Code zu schreiben wenn es ums Handling von fixed size containern geht. Die structured bindings können somit auch als lineare Fortsetzung von auto gesehen werden und helfen containertypen wie
std :: tuple
und
Std :: array
etwas „natürlicher“ in den Code einzubauen.