01 / Memory
HEAPThe memory left depends on what ran earlier. Allocation can fail, and fragmentation makes memory hard to use.
Embedded C++ library / Prototype 2.0
C++ built for bounded systems.
A C++17 header-only library for embedded software, where memory, timing, and failure behavior matter a great deal.
CORE IDEA: MAKE THE COST VISIBLE BEFORE THE CODE RUNS.
01 / The problem space
Many C++ features are useful on a desktop, but an embedded product has a much smaller budget and stricter timing.
01 / Memory
HEAPThe memory left depends on what ran earlier. Allocation can fail, and fragmentation makes memory hard to use.
02 / Control
EXCEPTIONSControl can jump across several stack frames, which is hard to see at the call site.
03 / Dispatch
VIRTUALVirtual interfaces add runtime indirection and make the call target less visible.
04 / Dependencies
STLLibrary facilities may rely on assumptions, headers, or runtime support that a target does not fit.
05 / Timing
UNKNOWNAny operation with unclear worst-case work makes timing analysis harder.
These tools are not "bad practices", but unsuitable for embedded work.
Before we adopt a feature, we ask: how much memory, how can it fail, and how much work can it do?
02 / Storage by design
The capacity is part of the type. The container owns its storage inside the object and does not silently grow.
#include "castle/container/vector.hpp" #include "castle/error/status.hpp" castle::container::vector<uint16_t, 16U> samples; if (samples.push_back(42U) == castle::status::ok) { // Use the stored sample. }
03 / Failure as data
A full buffer or an empty queue is a normal condition that the system must handle every day. CASTLE returns a status for the caller to check.
Continue with the accepted data.
Reject the item, apply backpressure, or record a diagnostic.
Handle “nothing to read” as an expected state.
Expected runtime outcomes use status or bool. Programmer-contract violations use the configured assertion policy.
04 / Abstraction with a budget
The goal is not to remove abstraction. It is to choose where work happens and make storage and dispatch costs inspectable.
Types and behavior are selected at compile time. The compiler can specialize the code without a runtime base-class lookup.
The function wrapper keeps a callable in a bounded local buffer. A callable that does not fit is rejected at compile time.
Core data structures avoid virtual dispatch. Observer, visitor, and delegate interfaces use it, so explain its cost and why it helps.
The same discipline applies to language and library features: no exceptions or RTTI, and no required STL containers or algorithms.
05 / The library shape
The core gives reusable building blocks. The extensions apply them to more specific jobs.
HEADER-ONLY / C++17 / NO REQUIRED STL
INCLUDE ONLY WHAT YOUR PLATFORM SUPPORTS
Some facilities use POSIX or Linux APIs, some use pthreads, and there is also a Zephyr GPIO backend.
06 / The daily coding shift
The code has a few more explicit lines. Those lines are not noise. They record decisions the system already had to make.
struct TickEvent {}; using dispatcher_type = castle::events::event_dispatcher< castle::events::event_tag_config<TickEvent, 4U, void(uint32_t)>>; dispatcher_type dispatcher; auto sub = dispatcher.register_callback<TickEvent>([](uint32_t){}); dispatcher.dispatch_event<TickEvent>(1U); sub.unsubscribe();
Before you add a buffer, decide its size. Before you add a callback, decide who owns it.
07 / Review mindset
We still check correctness and tests, but we also ask four resource questions.
Where does the storage live, and who owns it? Is it borrowed or owned by the object? What is the maximum size?
What happens when the queue is full, the buffer is empty, or a lookup fails? Is it handled or ignored?
What work happens in the worst case? Is that cost right for the task period?
Can this run from more than one task or an interrupt? Who protects shared state? Can a callback outlive its object?
A strong review explains these answers from the design and code, not only from a passing test.
08 / Impact and limits
CASTLE makes component behavior easier to reason about. It does not replace evidence at the system level.
What becomes easier
What still needs evidence
09 / Take it into the next change
When we review a component, we should explain how much memory it can own, what happens at its limit, and what work it can do.
01 Choose storage and capacity up front.
02 Make expected outcomes explicit.
03 Keep lifetimes and dispatch visible.
04 Measure timing and concurrency on the real target.
Fewer hidden decisions. More deliberate systems.
THANK YOU / CASTLE 2.0 / C++17 EMBEDDED TEMPLATE LIBRARY
APPENDIX / A · Method
Each CASTLE operation has an STL counterpart run under the same harness. The figures come from one captured run, so read them as a guide, not a statistical result.
How to read
Excluded from charts
APPENDIX / B · Overview
Most common operations sit at parity. The clearest CASTLE gains are string search, array access, and associative containers.
Ratio = STL ÷ CASTLE, CPU ns per op. Vertical line marks 1.00x (parity). Bar scale 0–2.2x.
Results within ±3% are treated as parity, so single-run noise is not read as a win.
APPENDIX / C · Containers
All eight associative operations are faster on CASTLE in this run, between about 1.17x and 1.32x.
CPU ns per op. Scale 0–1600 ns. Shorter bar is faster.
The source of each gap is not shown in this capture; check the implementation before naming a cause.
APPENDIX / D · Sequences and strings
Most rows are at parity. The notable gaps are string_find (2.1x faster on CASTLE) and small CASTLE deficits in queue push/pop and string construct.
Containers and stacks · 0–1000 ns
Small operations · 0–70 ns
Outside the highlighted rows the gaps are small, so speed alone should not drive the design choice.
Slide 1 of 14: CASTLE — C++ built for bounded systems