Wie an Weihnachten

Einfaches Auspacken von C++-Strukturen mit structured bindings

Container mit fixer Grösse auszupacken kann in C++ anstrengend sein. Doch diese Zeiten sind mit C++17 vorbei – Dank structured bindings.

21.03.2018Text: tnt-graphics0 Kommentare

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.

Menschen bei bbv

«Gute Ideen sind unser Rohstoff»

Agile Software Development
Technica Radar 2024

Mehr als KI: Das sind die wichtigsten IT-Trends 2024

AI/KI
6 Tipps, wie Sie Ihr Team effizienter machen

Müde Augen ade: So helfen kurze Codezeilen

Agile

Beachtung!

Entschuldigung, bisher haben wir nur Inhalte in English für diesen Abschnitt.

Achtung!

Entschuldigung, bisher haben wir für diesen Abschnitt nur deutschsprachige Inhalte.