ETL: STL-shaped containers that never touch the heap
Embedded Template Library
At a glance
- What is it?
- The Embedded Template Library gives C++ programmers fixed-capacity containers, message routing and finite state machines for targets where dynamic allocation is banned or the toolchain predates C++11.
- Who is it for?
- ETL is the right fit when allocation is off the table, when memory has to be accounted for at compile time, or when a toolchain still only speaks C++03, and the message router and finite state machine pieces save real work on top of that. It is the wrong choice for a server-side codebase that already has a modern standard library and plenty of memory, because ETL adds a second container vocabulary and a version scheme to maintain.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 13 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why a second container library is justified at all
The usual objection to a header-only C++ container library is that the standard one already exists, is free, and is better tested. The README answers this from the embedded side rather than arguing about features. C++ templates already give you flexibility and type safety, and the Standard Template Library is well tested, but it is frequently a poor fit for constrained targets: dynamic memory allocation is discouraged or outright forbidden in many embedded applications, which makes the standard containers impractical or unusable rather than merely inconvenient.
There is a second motivation that has quietly become the more important one. Many embedded toolchains still lack complete support for anything after C++03, so a codebase pinned to an old compiler cannot use `std::optional`, the newer type traits or the C++11 container improvements even though the hardware it runs on is modern. ETL exists to backport selected later-standard pieces to old compilers.
The stated design goals are worth reading as written, because they explain most of the library's API decisions. Containers get a fixed or maximum size defined at compile time. The APIs closely resemble STL ones so that existing code and habits transfer. Compatibility with C++98 is maintained while implementing features from C++11, 14, 17, 20, 23 and 26 where possible. Behaviour stays deterministic. And the library is explicit that it complements the STL rather than replacing it.
What the no-heap rule actually constrains
The claim that gets ETL onto embedded target lists is that the heap is never used. All non-intrusive containers have a fixed capacity, and storage is allocated either at compile time or on the stack, which means memory requirements can be resolved before the program runs. That is not only a memory saving. On a microcontroller, the absence of a heap removes a whole class of runtime failure, including fragmentation, allocation failure paths and the timing unpredictability that an allocator introduces.
The cost is that the size becomes a design decision rather than a runtime one. An `etl::vector` with a capacity set at compile time cannot grow, so code that would have pushed elements until memory ran out now fails in a specific, bounded way. If the workload is genuinely unbounded, that is the wrong abstraction and no amount of STL resemblance fixes it.
Two supporting design choices follow from the same constraint. Containers use contiguous memory layouts, which is what makes cache behaviour predictable on the target. And shared base classes, keyed by type, exist to hold code that would otherwise be duplicated across every container instantiation, which keeps the compiled footprint down. RTTI is not required at all, and virtual functions are used sparingly, only where they are strictly necessary. Header-only delivery means there is no separate compilation step and no library to link.
Error checking you pick rather than inherit
Almost every container library gives you one failure model. ETL makes it a configuration decision: the README lists asserts, exceptions, error handlers or no checks as the user's choice. Each option has a real cost on a target, which is why the choice is worth making deliberately rather than inheriting from a library.
Asserts cost code size and, in a release build, may cost the failure entirely if assertions are compiled out, so they are a development aid. Exceptions are the most convenient but pull in the runtime machinery that many embedded toolchains are built specifically to avoid, and a C++03 target is unlikely to support them at all. Error handlers let a project route failures into its own reporting path, which fits a system that already has a logging or fault channel. No checks is the fastest and the least safe, and it is a legitimate choice when the code is proven or the failure cannot be handled anyway.
The practical consequence is that the same container behaves differently across projects built from the same header. Worth settling before you commit, because porting an ETL-based module between a debug target and a production target can change which failures you ever hear about.
Backports are where most of the churn lives
The compatibility promise is expensive to keep, and the release notes show where the effort goes. Version 20.49.0, published on 2026-09-08, is a list of roughly two dozen pull requests: adding `etl::apply`, adding `difference_type` to span, supporting structured bindings for array, adding safe integer comparisons, clamping `n` in `string_view`'s `remove_prefix` and `remove_suffix`, fixing `variant` to be valueless after a throwing construction, and fixing an ODR violation in `optional`. It also cleans up type traits to use C++11 forms where possible.
Those entries are not incidental. Supporting C++03 through C++26 in one codebase means every modern feature has to be expressed in terms that the oldest supported compiler accepts, and every bug found in the modern path has to be backported through the older ones. The version number, currently in the 20.x range, is a good signal of how much surface that creates.
If you only need a handful of these features, the honest alternative is to upgrade the compiler instead of the library. ETL earns its keep on targets where the toolchain genuinely cannot move, not on a board that happens to have an old SDK installed.
Build files, framework ports and example programs
The README does not give an install command, which is correct for a header-only library. Integration paths are visible in the repository tree instead. There is a `CMakeLists.txt` for CMake users, a `meson.build` with `meson_options.txt` for Meson, and `BUILD.bazel` plus `MODULE.bazel` for Bazel, including a checked-in `.bazelversion`. PlatformIO users have `library.json`, and Arduino users have `library.properties` alongside a dedicated `arduino/` directory. A `zephyr/` directory points at Zephyr support, and the Conan Center recipe badge in the README covers the package manager route.
Headers live in `include/`, generated documentation sources in `docs/` and `hugo/`, and UML material in `uml/`. The `test/` directory and the README's claim of over 10,000 unit tests line up with the continuous integration badges, which cover GCC and Clang from C++11 through C++26 plus a separate syntax-check job.
The `examples/` directory is the most useful part for judging fit. Rather than toy snippets, it holds programs that look like firmware: `Blink` and `BlinkList`, a `Debounce` example, a `Scheduler`, `QueuedFSM`, message routers including `QueuedMessageRouter` and `MutexMessageRouter`, `UniquePtrWithPool`, and an `ArmTimerCallbacks` example. There is also a `platformio/` subdirectory. If your application resembles one of those, the library will fit; if it does not, the value drops sharply, because the parts unique to ETL are exactly those examples.
Where the STL or Boost is the better answer
Compare ETL against what you would otherwise reach for. On a Linux server or a modern desktop build, the standard library already provides fixed-capacity `std::array`, and for the common fixed-size needs ETL is redundant. Boost.Container offers configurable allocator templates and many other tuned containers for a general-purpose build. The difference is not capacity; it is that both assume a heap and a C++11-or-later compiler, which is exactly the pair of assumptions ETL rejects.
ETL is also not a drop-in namespace swap. Code written against `std::` needs its includes and qualified names changed, and container semantics differ at the edges where STL permits reallocation, which ETL cannot do. On a project where the STL is already a hard requirement for other reasons, ETL's familiarity claim only partly holds.
The third alternative is writing the two or three containers you actually need by hand, which for a fixed buffer and an index is often a day's work. That skips the compatibility maintenance entirely and is worth weighing for a small firmware module with one or two collection types.
Release cadence and what the project promises about support
The README describes the project as actively maintained on GitHub since 2014, and the repository's last push was on 2026-09-23. Releases are frequent and granular: 20.48.1 on 2026-07-11, followed by 20.49.0 on 2026-09-08, each listing the pull requests it contains. The licence is MIT, which is permissive and carries no obligation on how you deploy it, though it does require the notice to travel with the source.
Support is offered in three forms, all named in the README: free email support, a Slack group, and paid support on request. The documentation lives on a separate site at etlcpp.com rather than in the repository, and the README points there for integration help rather than documenting a build itself. That is the main thing to weigh against the project: the feature list is generous, the README is a good overview, and the actual integration instructions live one site away, so plan to read them before committing.
Editorial conclusion
ETL is the right fit when allocation is off the table, when memory has to be accounted for at compile time, or when a toolchain still only speaks C++03, and the message router and finite state machine pieces save real work on top of that. It is the wrong choice for a server-side codebase that already has a modern standard library and plenty of memory, because ETL adds a second container vocabulary and a version scheme to maintain. The deciding factor to check first is your error handling preference: the README makes asserts, exceptions, error handlers or no checks a configuration choice, and the one you pick changes every container call. Start at etlcpp.com for the integration guide, then read the release notes for 20.49.0, which show how much churn the compatibility promise creates.
Frequently asked questions
Does ETL allocate memory on the heap at runtime?
No. The README states that ETL avoids dynamic memory allocation entirely and that the heap is never used, with all storage allocated at compile time or on the stack. That applies to the non-intrusive containers, which all have a fixed capacity resolved at compile time.
Which C++ standards can ETL be compiled with?
The README says the library is compatible with any compiler that supports C++03 or later, and that it implements many C++11, 14, 17, 20, 23 and 26 features where possible. The CI badges cover GCC and Clang builds from C++11 up to C++26, plus a separate syntax-check job.
Is ETL a replacement for the C++ Standard Template Library?
No, and the README says so directly: ETL is not intended as a full replacement for the STL but as a complementary solution for embedded systems. It is designed to operate independently of the STL, with no STL dependency, and its APIs are shaped to resemble STL ones for familiarity.
How is ETL added to a CMake or PlatformIO project?
The repository tree carries the integration files rather than the README carrying a command: `CMakeLists.txt`, `meson.build`, `BUILD.bazel`, `library.json` for PlatformIO and `library.properties` for Arduino, with separate `arduino/` and `zephyr/` directories. The library is header-only, so headers under `include/` are what a build needs. The README points to etlcpp.com for integration help.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/etlcpp-etl)