zurück zum Artikel

C++23: Eine multidimensionale View

Rainer Grimm

(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.

Modernes C++ – Rainer Grimm
Rainer Grimm

Rainer Grimm ist seit vielen Jahren als Softwarearchitekt, Team- und Schulungsleiter tÀtig. Er schreibt gerne Artikel zu den Programmiersprachen C++, Python und Haskell, spricht aber auch gerne und hÀufig auf Fachkonferenzen. Auf seinem Blog Modernes C++ beschÀftigt er sich intensiv mit seiner Leidenschaft C++.

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;

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.

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.

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