
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):
template<typename T, std::size_t Extent = std::dynamic_extent>
class span;
T
är elementtypen.Extent
anger längden vid kompilering, ellerstd::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
#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>’
}
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
ellerstd::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):
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
#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:
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)
#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:
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 matchamdspan
'sElementType
),reference
– referenstypen som returneras avaccess(...)
,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 (somreference
),offset(data_handle_type, i)
– flyttar datahandtageti
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.
#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:
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.