# Så här använder du span-view i C++

17 augusti 2025 | https://www.ribomation.se/blog/2025/sa-har-anvander-du-span-view-i-c++/

C++ har traditionellt erbjudit flera sätt att representera en sekvens av element: pekare, rå-arrayer och olika STL-containrar. I C++20 tillkom `std::span`, och i C++23 byggdes konceptet ut till `std::mdspan`. Dessa är **icke-ägande vyer** över data som förenklar kod, minskar risken för buggar, och förbättrar prestanda.

`std::span<T>`
--------------

En `std::span` är en lättviktig vy över en sekvens av element av typen `T`. Den äger inte minnet, utan fungerar som ett “fönster” mot befintlig data. Så här ser definitionen ut (förenklad):

```cpp
template<typename T, std::size_t Extent = std::dynamic_extent>
class span;
```

*   `T` är elementtypen.
*   `Extent` anger längden vid kompilering, eller `std::dynamic_extent` (standard) om längden bestäms vid körning.

### Egenskaper

*   Äger inte data, bara en pekare och ett antal element.
*   Säker: håller reda på längden, så man undviker buffer overflow.
*   Kan skapas från arrayer, `std::vector`, `std::array`, eller pekare + längd.
*   Ger bekväm iteration, slicing och interoperabilitet med modern C++.

### Exempel

```cpp
#include <span>
#include <vector>
#include <array>
#include <list>
#include <print>
#include <string>
using std::string;

void process(string const& what, std::span<int const> s) {
    std::print("{:23}: [", what);
    for (std::size_t i = 0; i < s.size(); ++i) {
        std::print("{}", s[i]);
        if (i + 1 != s.size()) std::print(", ");
    }
    std::println("]");
}

int main() {
    // 1) std::vector
    auto v = std::vector<int>{1, 2, 3, 4, 5};
    process("std::vector", v); // implicit konvertering till std::span<const int>

    // 2) Slice av std::vector (första tre elementen)
    process("std::vector slice", std::span{v}.first(3));
    process("std::vector slice (alt)", std::span{v}.subspan(2, 3));

    // Alternativ: std::array
    auto sa = std::array<int, 3>{11, 12, 13};
    process("std::array", sa);

    // 3) Statisk array (C-array) – implicit konvertering till span
    int a[] = {7, 8, 9, 10};
    process("native static array", a);

    // 4) Dynamisk array (new[]), skapa span från pekare + längd
    const int n = 4;
    auto* dyn = new int[n]{20, 21, 22, 23};
    process("native dynamic array", {dyn, static_cast<std::size_t>(n)});
    delete[] dyn;

    //auto lst = std::list<int>{90, 91, 92};
    //process("std::list", lst);
    //error: could not convert ‘lst’ from ‘std::__cxx11::list<int>’ to ‘std::span<const int>’
}
```

```text
std::vector            : [1, 2, 3, 4, 5]
std::vector slice      : [1, 2, 3]
std::vector slice (alt): [3, 4, 5]
std::array             : [11, 12, 13]
native static array    : [7, 8, 9, 10]
native dynamic array   : [20, 21, 22, 23]

Process finished with exit code 0
```

### Nyttiga metoder

*   `size()`, `empty()`
*   `front()`, `back()`, `operator[]`
*   `subspan(offset, count)` – skapa ett nytt span över delmängd

### Krav på underliggande container

Eftersom `std::span` inte äger sitt data, så måste källan som span pekar på uppfylla vissa krav:

*   Data måste vara lagrad i ett minnesblock (contiguous).
*   Typiska kandidater är: C-array:er, `std::array`, `std::vector`.
*   Det fungerar inte med länkade datastrukturer såsom `std::list` eller `std::forward_list`.
*   Konstruktionen kräver antingen \[pekare, längd\] eller en container som erbjuder `data()` + `size()`.

Detta gör att `std::span` är mycket flexibel men samtidigt tydlig i sitt användningsområde: den är alltid en vy över ett sammanhängande block-minne.

* * *

`std::mdspan<T, Extents, LayoutPolicy, AccessorPolicy>`
-------------------------------------------------------

När `std::span` är endimensionell, så introducerar `std::mdspan` (C++23) möjligheten till **flerdimensionella vyer**. Den är särskilt användbar i numeriska beräkningar, bildbehandling och maskininlärning.

> I skrivande stund finns det ingen kompilator-version som har implementerat mdspan, så denna artikel-del nedan kan innehålla felaktigheter, eftersom jag inte kunnat test-köra exemplen för mdspan 😠

Definition (förenklad):

```cpp
template<
    typename T,
    typename Extents,
    typename LayoutPolicy = std::layout_right,
    typename AccessorPolicy = std::default_accessor<T>
> class mdspan;
```

*   `T` – typen på elementen.
*   `Extents` – beskriver dimensionernas storlek (`std::extents`).
*   `LayoutPolicy` – minneslayout (row-major, column-major, eller egen).
*   `AccessorPolicy` – hur man läser/skriv data (kan anpassas).

### Exempel: 2D matris

```cpp
#include <mdspan>
#include <vector>
#include <print>

int main() {
    constexpr auto rows = 3, cols = 4;
    auto data = std::vector<int>(rows * cols);
    for (int i = 0; i < data.size(); ++i) data[i] = i + 1; // fyll med 1..12

    auto m = std::mdspan(data.data(), rows, cols);
    for (int r = 0; r < rows; ++r) {
        for (int c = 0; c < cols; ++c) {
            std::print("{:3}", m(r, c));
        }
        std::println("");
    }
}
```

Utskrift:

```text
  1  2  3  4
  5  6  7  8
  9 10 11 12
```

### LayoutPolicy

*   `std::layout_right` (default) → C-liknande row-major.
*   `std::layout_left` → Fortran-liknande column-major.
*   Egen layout är möjlig för avancerade användningsfall.

### Exempel: Fortran-liknande layout (column-major)

```cpp
#include <mdspan>
#include <vector>
#include <print>

int main() {
    constexpr auto rows = 3, cols = 4;
    auto data = std::vector<int>(rows * cols);
    for (int i = 0; i < data.size(); ++i) data[i] = i + 1;

    // Column-major istället för row-major
    auto m = std::mdspan<int, std::extents<3,4>, std::layout_left>(data.data());
    for (int r = 0; r < rows; ++r) {
        for (int c = 0; c < cols; ++c) {
            std::print("{:3}", m(r, c));
        }
        std::println("");
    }
}
```

Här lagras elementen kolumnvis i minnet, vilket kan vara fördelaktigt om man arbetar med algoritmer eller bibliotek som förväntar sig Fortran-liknande layout.

Utskrift:

```text
  1  4  7 10
  2  5  8 11
  3  6  9 12
```

### AccessorPolicy

`AccessorPolicy` styr **hur element hämtas (och ev. skrivs)** från den underliggande datan. Standard är `std::default_accessor<T>`, som i praktiken gör ett vanligt pekarindex (`*(p + i)`). En egen accessor kan bland annat användas för att:

*   göra **read-only**\-vyer över icke-konst data,
*   lägga till **bounds-checking** eller andra kontrakt i debugbyggen,
*   implementera **alternativa representationer** (t.ex. komprimerade data) med ett proxy-objekt.

En accessor måste ange minst följande medlemmar:

*   `element_type` – elementtyp (ska matcha `mdspan`'s `ElementType`),
*   `reference` – referenstypen som returneras av `access(...)`,
*   `data_handle_type` – typen på datahandtaget (ofta en pekare),
*   `offset_policy` – en accessor-typ som kan användas för offset:ade vyer (ofta samma typ),
*   `access(data_handle_type, index)` – returnerar ett element (som `reference`),
*   `offset(data_handle_type, i)` – flyttar datahandtaget `i` steg framåt (t.ex. `p + i`).

Obs: `AccessorPolicy` påverkar **åtkomst**, inte minneslayout. Layout kontrolleras av `LayoutPolicy`.

Ett litet (_torr-simmat_) exempel nedan visar en accessor som returnerar element som read-only.

```cpp
#include <mdspan>
#include <vector>
#include <print>
#include <cmath>

// Minimal read-only accessor
struct ro_accessor {
    using element_type     = const int;          // matchar mdspan's ElementType
    using reference        = const int&;         // vad access() returnerar
    using data_handle_type = const int*;         // handtag till data
    using offset_policy    = ro_accessor;        // samma typ duger för offset

    constexpr reference access(data_handle_type base, std::size_t index) const noexcept {
        return base[index];
    }
    constexpr data_handle_type offset(data_handle_type base, std::size_t index) const noexcept {
        return base + index;
    }
};

int main() {
    constexpr auto rows = 2, cols = 3;
    auto data = std::vector<int>{1,2,3,4,5,6};

    // Vanlig läsbar vy (default accessor över const data)
    auto m_def = std::mdspan<const int, std::extents<rows, cols>>( data.data() );

    // Read-only vy via explicit accessor-policy
    auto m_ro = std::mdspan<const int, 
                            std::extents<rows, cols>, 
                            std::layout_right, ro_accessor>( data.data() );

    // Läsning fungerar
    for (int r = 0; r < rows; ++r) {
        for (int c = 0; c < cols; ++c) {
            std::print("{:3}", m_ro(r, c));
        }
        std::println("");
    }

    // Skrivförsök (m_ro(r,c) = 42;) kompilerar inte, eftersom reference är const int&
}
```

Utskrift:

```text
  1  2  3
  4  5  6
```

* * *

Prestanda och användningsområden
--------------------------------

Både `std::span` och `std::mdspan` är designade för **nollkostnadsabstraktioner** (_zero-const abstractions_): de ska vara lika snabba som att arbeta direkt med pekare och index.

Typ

Dimensioner

Ägande

Vanlig användning

`std::span`

1D

Nej

Funktionparametrar, slicing, vyer

`std::mdspan`

1D, 2D, …

Nej

Matrisberäkningar, bilddata, tensorer

* * *

Sammanfattning
--------------

*   **`std::span`** (C++20) ger ett säkert och smidigt sätt att arbeta med sekvenser utan att kopiera data.
*   **`std::mdspan`** (C++23) utökar konceptet till flera dimensioner, med stöd för olika minneslayouter.
*   Båda är **icke-ägande vyer** som minskar buggar och ökar prestanda i modern C++.

När du skriver generiska funktioner som arbetar på data, välj `span` eller `mdspan` istället för råpekare — det gör din kod både robustare och mer uttrycksfull.
