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:

CompilerMax C standardMax C++ standardComments
GCC (Embedded)C23C++23 (partial)Full-featured, widely used, embedded features may lag
Keil C51C99 (subset)NoneLegacy
Keil MDK (ARM)C23C++20Modern backend over its older version
MPLAB XC8/XC16C99NoneFor PIC, 8/16-bit
MPLAB XC32C17C++14Based on GCC
Green Hills MULTIC11/C17C++17/20Safety-critical, commercial
TI Code ComposerC11–C23C++20Multiple backends
NXP MCUXpressoC23C++20
Renesas e² StudioC99 (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.