short

Portable SIMD is in C++26, and you can try it now

· english· audience: working-cpp· AI-generated, reviewed by Filip Sajdak

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_mask type, 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.