Portable SIMD is in C++26, and you can try it now
Writing SIMD by hand means picking a side. Intrinsics like _mm256_add_ps are precise and fast, but they tie the code to one instruction set. A NEON port then becomes a second implementation behind an #ifdef maze. The alternative is plain loops and a hope that the auto-vectorizer cooperates. That works until it silently stops, usually because of a pointer alias the compiler cannot rule out.
C++26 standardises the middle path: <simd>. It is a portable vector type. The element type and the width are part of the type, and the ordinary arithmetic operators do the work.
std::simd<float> a = 1.5f;
std::simd<float> b = 2.0f;
auto c = a * b; // one instruction, whatever the target supports
The library picks a native width for the target, so the same source compiles to SSE, AVX-512, or NEON. It needs no intrinsics and no #ifdef per architecture. Vectorization is explicit, not a hopeful side effect of -O3.
What runs today
GCC 16.1’s libstdc++ does not yet provide the standard <simd> header, and __cpp_lib_simd is undefined there. It does provide the Parallelism TS version that the standard type grew out of. That version has the same programming model under a different name:
width = 4
3 3 3 3
On Compiler Explorer’s baseline x86-64 target a vector holds four floats, and one multiply gives four results. Compile the same code with -march=x86-64-v3 and the width becomes 8. On an ARM machine the code uses NEON. The width belongs to the target, which is exactly the point: you write the operation, and the library picks the register.
Migrating later is mostly renaming. The type std::experimental::native_simd<float> becomes std::simd<float>. Three things carry over unchanged:
- the arithmetic operators
- the
simd_masktype, for lane-wise conditionals - the load and store helpers
When it is the right tool
The <simd> header is for data-parallel arithmetic where you already know the loop vectorizes in principle. Typical uses:
- pixel and audio buffers
- physics steps
- distance and dot-product kernels
- anything shaped like “apply this to every element”
A simd type makes vectorization guaranteed and visible instead of dependent on the optimizer’s mood. It is also far easier to read six months later than a page of intrinsics.
It does not replace the auto-vectorizer on ordinary loops. It will not rescue code whose real problem is memory bandwidth or a dependency chain. For that last case, the assembly and llvm-mca will tell you before you rewrite anything.
Sources: P1928 “std::simd” · cppreference: std::simd · GCC 16 release notes.