Templates and Generic Code
Introduction
In C you wrote int_max and float_max, or abused macros. Templates let you write one function or class that works for many types — the compiler generates the versions you actually use. That is powerful for ring buffers, filters, and containers; it also increases flash if you instantiate many types.
This article explains how templates work, when they help embedded code, and when to stick with plain C or a single typed implementation.
Function templates — one pattern, many types
1 2 3 4 5 6 7 | |
The compiler generates max_value<int> and max_value<float> — two functions. You write once; the compiler specializes.
In plain terms
A template is a cookie cutter. You draw one shape; the machine stamps dough for each type you request. Each stamp uses flash — only stamp shapes you need.
Class templates — generic containers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
One implementation — two buffer types. Size N is a template parameter — often known at compile time (no heap).
Compare Data structures in C: same FIFO idea, type-safe without void *.
Template parameters — type vs value
1 2 3 | |
Non-type parameters are ideal for fixed buffer sizes on MCU — stack or static arrays inside the class, visible to optimizer.
constexpr — compile-time constants
1 2 3 4 5 6 7 | |
constexpr means "computable at compile time" (C++11+). Use for pin masks, table sizes, and static checks — zero runtime cost when used in constant contexts.
Code size trade-off
Each template instantiation is separate machine code:
1 2 3 | |
| Strategy | When |
|---|---|
| Template on one type you need | Sensor pipeline all int16_t |
Explicit instantiation in one .cpp |
Control .o size |
| Non-template C function | Shared library, many types via void * |
#if / type alias |
Only two variants in product |
Always size firmware.elf after adding templates.
inline and headers
Template definitions usually live in headers — compiler needs full body to instantiate:
1 2 3 4 5 | |
Every .cpp that includes the header can generate code — link one explicit instantiation if needed, or keep template entirely in header for small functions.
SFINAE and concepts — awareness only
Advanced metaprogramming (enable_if, C++20 concepts) filters which types can use a template. Robotics code on MCU rarely needs this in year 2–3 — know it exists when library errors look like novel length.
Templates vs macros
| Macro | Template | |
|---|---|---|
| Type check | No | Yes |
| Debugger | Poor | Better |
| Code bloat | Text paste | Per instantiation |
| Suitable for | Register bit masks | Algorithms, buffers |
Prefer templates over function-like macros for min/max/clamp in C++.
Embedded examples
- Fixed ring buffer for UART DMA (template
N) - Moving average filter
template<typename T, int Window> std::array<T, N>instead ofT arr[N]with size in type- Eigen / etl::vector on larger targets — not on 32 KB RAM MCU
Relevant topics
Starting points
- Implement
template<typename T> T clamp(T v, T lo, T hi)— instantiate forintandfloat; compare.elfsize. - Replace a
#define MAX(a,b)with templatemax_value. - Use
std::array<uint8_t, 64>in a hosted test build. - Count instantiations in map file if linker supports it.
Focus points
- Templates live in headers unless explicitly instantiated.
- Flash cost scales with instantiations — not free abstraction.
- Keep template parameters small on MCU — prefer
uint16_tpipeline over generic everything. - Readable names —
template<typename T>is fine;typename SampleTypeclearer in APIs.
Key points
- Templates generate type-specific code at compile time from one pattern.
- Class templates build generic buffers and containers with compile-time sizes.
constexprmoves computation to compile time when possible.- On embedded, measure flash and prefer templates only where duplication pays off.