Published On
Oct 1, 2026
Read Time
7 mins
By Micael Borgefeldt
Published On
Oct 1, 2026
Read Time
7 mins
TL;DR
Exploring the C++20 features that embedded teams can confidently deploy today with modern compiler and tooling support.
C++23 has been published and C++26 is close behind. Yet walk into many firmware teams and you'll find code written in C++11, or in C with a thick layer of macros. The reason usually isn't a lack of interest. It's that an embedded team can only adopt a language standard once the whole toolchain around it is ready.
The compiler has to support it. The debugger has to understand it. Static analysis tools and coding standards have to cover it. For safety-critical products, the certification process has to accept it. A language feature that fails any of those tests stays off the table, however attractive it looks.
Put that way, the relevant question for embedded teams is not which C++ standard is newest. It's which is the newest standard they can confidently deploy. For most teams today, that answer is C++20.
Embedded development runs on a different timeline from desktop and server software. Products stay in the field for ten years or more, and every toolchain change has to be validated before it touches production code. By the time a new standard is widely implemented and supported by analysis tools, many organizations are only beginning large-scale adoption of the previous one.
At the same time, the pressure to move forward has grown. Firmware now handles connectivity, remote updates,functional safety, rich user interfaces, and in some cases edge AI workloads. As codebases grow, the hardest engineering problem is often keeping the code maintainable over a long product life rather than making a feature work in the first place. Stronger type safety and clearer abstractions help with exactly that.
C++20 is where those two forces meet. It offers a meaningful step forward in expressiveness and safety, and it has had enough time to mature across compilers and tools.
Not every C++20 feature is equally useful on a microcontroller. The ones embedded teams tend to adopt first all have one thing in common: they work at compile time and add nothing to the binary.
std::bit_cast and the other functions in the bit header replace unsafe type punning and hand-written bit manipulation with well-defined standard code.Here is how concepts and std::span work together in practice. This logging function accepts any peripheral that provides a write operation:
#include <concepts>
#include <cstddef>
#include <span>
template <typename T>
concept Uart = requires(T port, std::byte b) {
port.write(b);
};
void log(Uart auto& port, std::span<const std::byte> msg) {
for (auto b : msg) {
port.write(b);
}
}
Pass a type that doesn't satisfy Uart and the compiler rejects it immediately, naming the requirement that wasn't met. The span carries its own length, so the function can't be called with a mismatched buffer size. The generated code is the same tight loop you would write by hand.
Some C++20 features deserve more caution. Coroutines can allocate their state on the heap unless the compiler can prove otherwise, which matters on systems that forbid dynamic allocation. Modules are powerful, but build tool support across the wider ecosystem is still settling. Both are worth evaluating, but neither is where most teams should start.
These are the two questions embedded engineers ask first, and they deserve direct answers.
On code size, the features listed above cost nothing at runtime. The usual concerns are exceptions and RTTI, which can increase application size. The IAR C/C++ Compiler lets you turn both off with the --no_exceptions and --no_rtti options, so each project enables only what it needs. Even when they are enabled, the actual overhead depends on how they are used, not simply on whether they are available.
On dynamic memory, the answer depends on which parts of the standard library you use:
std::array, std::span, std::string_view, std::optional, and std::variant never allocate.std::vector, std::string, and std::function can allocate, and exception handling may use dynamic memory depending on the implementation.Teams with strict no-heap rules typically restrict themselves to the first group, or supply their own allocators.
The same distinction matters for determinism. Compile-time features have no effect on interrupt latency or timing. Anything that can allocate or throw should stay out of interrupt service routines and hard real-time paths, exactly as it would in well-written C.
The IAR C/C++ Compiler accepts C++20 source code by default and is largely compliant with ISO/IEC 14882:2020. Combined with the Libc++ library, it gives access to the vast majority of C++20 language and library functionality.
Support extends beyond the compiler. The IAR C-SPY Debugger displays STL containers in readable form, and it can stop execution when an exception is thrown or escapes without a handler. That makes modern C++ code as inspectable on the target as plain C.
For teams in automotive, industrial, and medical markets, language features only matter if they fit the safety process.
The current coding standard for safety-related C++, MISRA C++:2023, is based on C++17. C++20-specific constructs therefore fall outside its explicit guidance, and teams need to decide how to treat them, often by restricting use to features with clear, well-understood behavior. IAR C-STAT checks code against MISRA C++, helping teams enforce whichever subset they settle on.
To validate the examples used in this article, we compiled, linked, and executed the code using the IAR LLVM C/C++ Compiler with Libc++ enabled. Features including Concepts, std::span, std::bit_cast, std::source_location, std::optional, std::variant, compile-time CRC table generation, constexpr, consteval, and constinit compiled and executed successfully on a Cortex-M target. The same application was also built through a standard CMake-based workflow using CMake Presets, providing a practical demonstration of modern embedded development with C++20.
Want to try it yourself? Access the complete example project, including the source code, CMakeLists.txt, and CMakePresets.json, here.

Figure 1. The Embedded C++20 Demo executing in IAR Embedded Workbench for Arm after being imported from a CMake project. The example showcases modern C++20 features running successfully on a Cortex-M target using the IAR LLVM C/C++ Compiler with Libc++ enabled.
For technical leaders, the good news is that adoption doesn't have to be all or nothing. Existing C and older C++ code continues to compile alongside new C++20 code, so teams can introduce newer features module by module, starting with new drivers or components, while existing products keep shipping.
The business case builds over time. Code that uses concepts, span, and constexpr is easier to review and harder to misuse, which lowers maintenance costs across a product life measured in decades. It also helps with people: engineers entering the field learn C++ from C++11 onward, and a codebase that looks like the C++ they know is easier to join and contribute to.
Because the IAR Platform brings the compiler, debugger, and static analysis together in one workflow, those improvements reach the whole toolchain at once rather than one tool at a time.
The quickest way to judge C++20 for your project is to compile some of your own code with it and look at the output.
Start a free evaluation of IAR Embedded Workbench or explore the complete workflow in the interactive IAR Embedded Development Platform demo.