C++23: Eine multidimensionale View
Der neue Standard der Programmiersprache führt mit std::mdspan eine nicht besitzende multidimensionale View einer zusammenhängenden Folge von Objekten ein.
- Rainer Grimm
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)