Maybe I caught your attention.
If you’re someone who switches between C and C++ regularly, you might recognize the mental gymnastics it takes to adjust between the two—along with a twinge of longing for some of C++’s more “polite” features. I won’t go so far as to call them “secure” (that might be overstating it), but modern C++ does offer conveniences that traditional C often makes you work hard for.
One such feature is std::span, introduced in C++20. And if you’re a C programmer who hasn’t tried it yet, you might be missing out on something you’ve wanted for years: a simple, elegant way to pass an array and its size as a single, cohesive unit.

Why std::span Matters
std::span is a lightweight, non-owning view over a contiguous block of memory. In C++, you can create it from arrays, std::vector, or raw pointers. It lets you:
- Pass both a pointer and size in one object
- Avoid manual length tracking
- Still access the raw array (.data()[index]) just like in C.
- Optionally get bounds-checked access (.at(index) or range-based for-loops)
It’s the kind of pattern C programmers have rolled manually for decades—and now it’s part of the language. So why still past the array pointer and length. Upscale.
Can You Just Jump to C++20?
That depends. The compiler ecosystem is diverse, especially in the embedded world. Some toolchains are already C++20-ready, while others—especially those for small or legacy microcontrollers—still lag behind without current expectation to modernize.
Here’s a snapshot of C and C++ standard support across commonly used compilers:
Compiler | Max C standard | Max C++ standard | Comments |
GCC (Embedded) | C23 | C++23 (partial) | Full-featured, widely used, embedded features may lag |
Keil C51 | C99 (subset) | None | Legacy |
Keil MDK (ARM) | C23 | C++20 | Modern backend over its older version |
MPLAB XC8/XC16 | C99 | None | For PIC, 8/16-bit |
MPLAB XC32 | C17 | C++14 | Based on GCC |
Green Hills MULTI | C11/C17 | C++17/20 | Safety-critical, commercial |
TI Code Composer | C11–C23 | C++20 | Multiple backends |
NXP MCUXpresso | C23 | C++20 | |
Renesas e² Studio | C99 (GCC) | C++20 (GCC) | Many backend options |
As you can see, many modern compilers support C11 or later, and some support C++20 out of the box. If your compiler is ready, moving forward could be as simple as flipping a flag, compiling and verifying/experimenting.
But What If You’re on a Legacy MCU?
Here’s where things get tricky. If you’re stuck with an older architecture—especially in the 8- or 16-bit world—your compiler might not support modern standards. The leap from C to C++ can be steep, and even using C11 might be a stretch.
Still, there’s room to experiment. You can bring the concept of a span to C with just a few lines of code. It won’t be as feature-rich, but it captures the spirit.
// Example for a type of span.
typedef struct {
int *data;
size_t size;
} int_span;
inline int get(const int_span s, size_t i) {
return ((i < s.size) ? s.data[i]) : abort(), 0;
}
You could even generalize this pattern using macros to simulate type-generic spans. It’s not perfect, but it’s a practical bridge.
Try Before You Port
If you’re maintaining a legacy C99 or even C89 codebase, consider building small side projects with newer versions of C or C++. This gives you hands-on experience with features like std::span, structured bindings, or constexpr—without forcing a massive migration right away.
You don’t have to jump into the deep end. But now’s a good time to dip your toes in.
Final Thought
std::span is a small feature with a big impact. It elegantly solves a problem that every C programmer has worked around for years. If you’re working in a modern toolchain, give it a try. And if you’re not? Try mimicking it in your own way.
It might just be the start of your journey toward more modern, expressive code—even in C.