C++26 Embedded MAVLink RP2040

C++26 for Embedded Systems

From C spaghetti to zero-cost abstractions on MAVLink + RP2040

A practical look at what C++26 brings to firmware development,
and what you can ship today with GCC 15 and -std=c++20

The Vision

C++26 and GCC 16 are just a few days (maybe weeks) away. Let's play the game: "what if C++26 and GCC 16 are already here?" -- because it takes embedded C++ much further.

Contracts, inplace_vector, and reflection are game changers:

Contracts

pre/post replace hand-written assert() with standard, toolchain-aware checks. Strip them in release = zero overhead.

inplace_vector

Finally a standard resizable container with no heap. Perfect for MAVLink packet buffers.

Reflection

Auto-generate serialization, debug logs, enum-to-string, all at compile time, zero runtime cost.

C++26 Code future no compiler yet

Based on the C++26 draft. When compilers catch up, we'll write it this way:

#include <contracts>
#include <inplace_vector>
#include <meta>

template<typename T>
concept Handler = requires(T t) {
    t.init();
    t.exec();
};

// C++26: resizable container, ZERO heap allocation
// perfect for MAVLink packet buffers
std::inplace_vector<uint8_t, 280> mavlink_buf;

struct MavlinkHandler {
    // C++26 contracts: preconditions checked at build/debug
    void parse(uint8_t* data, size_t len)
        pre(data != nullptr)
        pre(len <= 280)
    {
        mavlink_buf.assign(data, data + len);
    }
    void init();
    void exec();
};

// C++26 reflection: auto-generate debug logs
// for all handler types, zero boilerplate
template<Handler H>
void log_handler_info() {
    constexpr auto name = std::meta::name_of(^H);
    Serial.print(name.data());
    Serial.println(" initialized");
}

template<Handler H1, Handler H2>
struct App {
    H1 mavlink;
    H2 sensors;

    void run() {
        mavlink.init();
        sensors.init();
        log_handler_info<H1>();
        log_handler_info<H2>();
        multicore_launch_core1([this] { sensors.exec(); });
        while (true) { mavlink.exec(); __wfi(); }
    }
};

Why C++26 Matters for Embedded

Contracts

pre / post replace hand-written assert() with standard, toolchain-aware checks. Strip them in release = zero overhead. The compiler and static analyzers can reason about them.

inplace_vector

A standard resizable container with fixed capacity and no heap allocation. Drop-in replacement for hand-rolled ring buffers. Perfect for MAVLink packet buffers on microcontrollers.

Reflection

Auto-generate serialization, debug logs, enum-to-string -- all at compile time with zero runtime cost. No more macro hacks or code generators.

No globals, no vtables, no heap, no RTTI. Just the way embedded should be.

Compiler Reality Check

As of April 2026, no compiler supports all C++26 features for production use.

FeatureStandardGCC 15GCC 16 (trunk)
ConceptsC++20YesYes
constexpr placement newC++26YesYes
=delete("reason")C++26YesYes
Pack indexingC++26YesYes
#embedC++26YesYes
Contracts (pre/post)C++26NoYes
inplace_vectorC++26NoYes
Reflection (meta)C++26NoNo

Since no compiler fully supports C++26 today, the next slide shows what you can ship right now.

What You Can Ship Today GCC 15 -std=c++20

#include <array>
#include <cstdint>
#include <cstddef>

// zero-cost interface check, no vtable
template<typename T>
concept Handler = requires(T t) {
    t.init();
    t.exec();
};

// heap-free resizable buffer for MAVLink packets
template<typename T, size_t N>
struct FixedVec {
    std::array<T, N> buf{};
    size_t len{};
    constexpr void push(T v) { buf[len++] = v; }
    constexpr T& operator[](size_t i) { return buf[i]; }
    constexpr size_t size() const { return len; }
    constexpr T* data() { return buf.data(); }
};

struct MavlinkHandler {
    FixedVec<uint8_t, 280> rx_buf;
    void init() { Serial.begin(57600); }
    void exec() { /* parse mavlink */ }
};

struct SensorsHandler {
    void init() { /* setup sensors */ }
    void exec() { /* read + send */ }
};

// no globals, no pointers, no RTTI
template<Handler H1, Handler H2>
struct App {
    H1 core0;
    H2 core1;

    void run() {
        core0.init();
        core1.init();
        multicore_launch_core1([this] { core1.exec(); });
        while (true) { core0.exec(); __wfi(); }
    }
};

// one line in main
App<MavlinkHandler, SensorsHandler> app;
void setup() { app.run(); }
void loop() {}

Ship Today, Upgrade Tomorrow

Today (GCC 15 / C++20)

  • Concepts for zero-cost interface checks
  • FixedVec as heap-free buffer
  • Templates for compile-time polymorphism
  • No globals, no vtables, no RTTI

Tomorrow (GCC 16 / C++26)

  • Swap FixedVec for std::inplace_vector
  • Add contracts (pre/post) -- zero code rewrite
  • Reflection for auto-generated logging
  • Same architecture, more guarantees
#EmbeddedSystems #Firmware #QGroundControl #MAVLink #Cpp26
← Swipe to navigate →