C++23: Eine multidimensionale View
(Bild: SergioVas/Shutterstock)
Der neue Standard der Programmiersprache fĂŒhrt mit std::mdspan eine nicht besitzende multidimensionale View einer zusammenhĂ€ngenden Folge von Objekten ein.
Ein std::mdspan ist eine nicht-besitzende multidimensionale View einer zusammenhĂ€ngenden Folge von Objekten. Dabei kann es sich um ein einfaches C-Array, einen Zeiger mit einer GröĂe, ein std::array, ein std::vector oder ein std::string handeln.
Oft wird diese multidimensionale View auch als multidimensionales Array bezeichnet.
Die Anzahl der Dimensionen und die GröĂe der einzelnen Dimensionen bestimmen die Form des mehrdimensionalen Arrays. Die Anzahl der Dimensionen wird Rang (rank) genannt und die GröĂe jeder Dimension Erweiterung (extension). std::mdspan's GröĂe ist das Produkt aus allen Dimensionen, die nicht 0 sind. Man kann auf die Elemente eines std::mdspan mit dem mehrdimensionalen Indexoperator [] zugreifen.
Jede Dimension eines std::mdspan kann einen static extent oder einen dynamic extent haben. static extent bedeutet, dass ihre LĂ€nge zur Compile-Zeit festgelegt wird; dynamic extent bedeutet, dass ihre LĂ€nge zur Laufzeit festgelegt wird.
Hier ist die Definition eines std::mdspan:
template<
class T,
class Extents,
class LayoutPolicy = std::layout_right,
class AccessorPolicy = std::default_accessor<T>
> class mdspan;
T: die zusammenhĂ€ngende Folge von Objekten,Extents: gibt die Anzahl der Dimensionen als deren GröĂe an; jede Dimension kann einen static extent oder einen dynamic extent haben.LayoutPolicy: legt die Layout-Richtlinie fĂŒr den Zugriff auf den zugrunde liegenden Speicher fest.AccessorPolicy:legt fest, wie die zugrunde liegenden Elemente referenziert werden.
Dank Klassen-Template Argument Deduktion (CTAG) in C++17 kann der Compiler die Template-Argumente oft automatisch aus den Datentypen der Initialisierer ableiten:
// mdspan.cpp
#include <mdspan>
#include <iostream>
#include <vector>
int main() {
std::vector myVec{1, 2, 3, 4, 5, 6, 7, 8}; // (1)
std::mdspan m{myVec.data(), 2, 4}; // (2)
std::cout << "m.rank(): " << m.rank() << '\n'; // (4)
for (std::size_t i = 0; i < m.extent(0); ++i) { // (6)
for (std::size_t j = 0; j < m.extent(1); ++j) { // (7)
std::cout << m[i, j] << ' '; // (8)
}
std::cout << '\n';
}
std::cout << '\n';
std::mdspan m2{myVec.data(), 4, 2}; // (3)
std::cout << "m2.rank(): " << m2.rank() << '\n'; // (5)
for (std::size_t i = 0; i < m2.extent(0); ++i) {
for (std::size_t j = 0; j < m2.extent(1); ++j) {
std::cout << m2[i, j] << ' ';
}
std::cout << '\n';
}
}
In diesem Beispiel wende ich Klassen-Template Argument Deduktion dreimal an. In (1) wird sie fĂŒr einen std::vector verwendet und in (2) und (3) fĂŒr ein std::mdspan. Das erste zweidimensionale Array m hat die Form (2, 4), das zweite m2 hat die Form (4, 2). In (4) und (5) werden die RĂ€nge der beiden std::mdspan angezeigt. Dank der Ausdehnung jeder Dimension (6 und 7) und des Indexoperators in (8) ist es ganz einfach, durch mehrdimensionale Arrays zu iterieren.
Wenn ein mehrdimensionales Array ein static extent haben soll, muss man die Template-Argumente angeben.
// staticDynamicExtent.cpp
#include <mdspan>
#include <iostream>
#include <string>
#include <vector>
#include <tuple>
int main() {
std::vector myVec{1, 2, 3, 4, 5, 6, 7, 8};
std::mdspan<int, std::extents<std::size_t, 2, 4>>
m{myVec.data()}; // (1)
std::cout << "m.rank(): " << m.rank() << '\n';
for (std::size_t i = 0; i < m.extent(0); ++i) {
for (std::size_t j = 0; j < m.extent(1); ++j) {
std::cout << m[i, j] << ' ';
}
std::cout << '\n';
}
std::cout << '\n';
std::mdspan<int, std::extents<std::size_t, std::dynamic_extent,
std::dynamic_extent>> m2{myVec.data(), 4, 2};// (2)
std::cout << "m2.rank(): " << m2.rank() << '\n';
for (std::size_t i = 0; i < m2.extent(0); ++i) {
for (std::size_t j = 0; j < m2.extent(1); ++j) {
std::cout << m2[i, j] << ' ';
}
std::cout << '\n';
}
std::cout << '\n';
}
Das Programm staticDynamicExtent.cpp basiert auf dem vorherigen Programm mdspan.cpp und erzeugt die gleiche Ausgabe. Der Unterschied ist, dass der std::mdspan m (1) einen static extent hat. Der VollstÀndigkeit halber: std::mdspan m2 (2) hat einen dynamic extent. Dementsprechend wird die Form von m mit Template-Argumenten angegeben, die Form von m2 jedoch mit Funktionsargumenten.
Layout-Strategie
Ein std::mdspan ermöglicht es dir, die Layout-Strategie fĂŒr den Zugriff auf den zugrunde liegenden Speicher anzugeben. StandardmĂ€Ăig wird std::layout_right (C-, C++- oder Python-Stil) verwendet, du kannst aber auch std::layout_left (Fortran- oder MATLAB-Stil) angeben. Die folgende Grafik veranschaulicht, in welcher Reihenfolge auf die Elemente des std::mdspan zugegriffen wird.
Das Durchlaufen von zwei std::mdspan mit den Layout-Strategien std::layout_right und std::layout_left zeigt den Unterschied.
// mdspanLayout.cpp
#include <mdspan>
#include <iostream>
#include <vector>
int main() {
std::vector myVec{1, 2, 3, 4, 5, 6, 7, 8};
std::mdspan<int, std::extents<std::size_t, // (1)
std::dynamic_extent, std::dynamic_extent>,
std::layout_right> m{myVec.data(), 4, 2};
std::cout << "m.rank(): " << m.rank() << '\n';
for (std::size_t i = 0; i < m.extent(0); ++i) {
for (std::size_t j = 0; j < m.extent(1); ++j) {
std::cout << m[i, j] << ' ';
}
std::cout << '\n';
}
std::cout << '\n';
std::mdspan<int, std::extents<std::size_t, // (2)
std::dynamic_extent, std::dynamic_extent>,
std::layout_left> m2{myVec.data(), 4, 2};
std::cout << "m2.rank(): " << m2.rank() << '\n';
for (std::size_t i = 0; i < m2.extent(0); ++i) {
for (std::size_t j = 0; j < m2.extent(1); ++j) {
std::cout << m2[i, j] << ' ';
}
std::cout << '\n';
}
}
Der std::mdspan m verwendet std::layout_right (1), der andere std::mdspan std::layout_left (2). Dank der Deduktion der Klassen-Template-Argumente benötigt der Konstruktoraufruf von std::mdspan (2) keine expliziten Template-Argumente und ist Àquivalent zum Ausdruck std::mdspan m2{myVec.data(), 4, 2}.
Die Ausgabe des Programms zeigt die beiden unterschiedlichen Layout-Strategien:
Die folgende Tabelle gibt einen Ăberblick ĂŒber die Schnittstelle von std::mdspan md.
Wie geht's weiter?
C++20 bietet keine konkreten Koroutinen an, aber ein Framework fĂŒr deren Implementierung von. Das Ă€ndert sich mit C++23. std::generator ist die erste konkrete Koroutine.
(rme [1])
URL dieses Artikels:
https://www.heise.de/-9299235
Links in diesem Artikel:
[1] mailto:rme@ix.de
Copyright © 2023 Heise Medien