# lager, unidirectional data flow and value semantics for C++

> arximboldi/lager is a header-only C++17 library that brings the Elm and Redux model to C++, with immutable state, pure update functions, dependency injection and a time-travelling debugger. It is the work of Sinusoidal Engineering, and the README points to the immer library as its immutability foundation.

**arximboldi/lager** —  C++ library for value-oriented design using the unidirectional data-flow architecture — Redux for C++

- Repository: https://github.com/arximboldi/lager
- Website: https://sinusoid.es/lager/
- Stars: 769 · Forks: 73
- Language: C++
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/arximboldi-lager

## What lager is and where the idea comes from

lager is a C++ library that assists value-oriented design by implementing the unidirectional data-flow architecture. The README says it is heavily inspired by Elm and Redux, and that it enables composable designs by promoting the use of simple value types and testable application logic via pure functions.

The phrase in the description that gets quoted is that you get time-travel for free, and that is a fair summary of what the architecture buys.

The project is by Sinusoidal Engineering, with the author listed as juanpe, and it sits inside a stated long-term vision of making interactive and concurrent C++ programs easier to write. The project has an accompanying sponsorship page and a Patreon link in the README, which tells you it is the work of a small group with a long horizon rather than a company product.

The talk history gives the intellectual context. There is a CppCon 2018 talk called The most valuable values, a C++ on Sea 2019 talk on postmodern immutable data-structures, and a CppRussia-Piter 2019 talk called Squaring the circle. The 2018 talk is the origin story, since it is where the author's earlier work on bringing immutable value semantics to C++ was presented, and lager is the application layer built on top of that foundation.

The documentation lives at sinusoid.es/lager, and the README points to an architecture overview section there as the guided introductory tour with code samples.

## The argument against model view controller, in the author's terms

The Why section of the README is the clearest statement of the design case, and it is worth reading because it identifies specific failure modes rather than asserting a preference.

The target is most interactive software written with an object-oriented interpretation of the Model View Controller design. The README credits that architecture with nice separation of concerns, a core application logic separate from the UI, and a good sense of modularity. What it criticises is the reliance on stateful object graphs, which makes software hard to test or parallelise, and the reliance on fine-grained callbacks, which makes composition hard and results in subtle problems that are hard to debug.

The response is value-based unidirectional data flow, and the README lists what that buys in three parts.

Concurrency is the first, and the mechanism is specific. Thanks to immutability and value types, threads can operate on their local copies of the data without mutexes or other flaky synchronisation mechanisms, and worker threads communicate results back by dispatching actions to the main thread.

Testability is the second. The application logic is made of pure functions that can be easily tested and are fully reproducible, and they interact with the world via special side-effect procedures loosely coupled to the services they need through dependency injection.

Structure is the third. Data and call graphs are always trees or DAGs rather than cyclical graphs, with explicit composition that is easy to trace and debug, and because you can always snapshot the state, undo and time travel become straightforward.

That last point is the one that distinguishes this from a generic architecture argument. A pure reducer over an immutable state value is trivially reversible, and time travel falls out of the representation rather than being a feature bolted on later.

## Dependencies, the header-only layout and building it

The library is written in C++17 and needs a compliant compiler and standard library. The README says it is continuously tested with GCC 7, while noting it might work with other compilers and versions.

It depends on Zug and Boost Hana, and the README notes that some optional extensions and modules may have other dependencies documented in their own sections. Boost Hana in particular is the metaprogramming library, and it is the heavy part of the dependency set: type-level computation for the reducers and actions.

The library is header only, which means you can copy the lager subfolder somewhere in your include path and use it, with no linking step. The README notes that some components, such as the time-travelling debugger, also require the installation of extra files.

If you would rather install it system-wide, the README gives a CMake path. You clone manually, then create the build directory and install:

```bash
mkdir -p build && cd build
cmake .. && sudo make install
```

For development there is a different path, and the recommended one uses the Nix package manager. At the root of the repository you type nix-shell, which the README says downloads all required dependencies and creates an isolated environment where you can use those dependencies without polluting your system. That matters for a project with a Boost dependency and a metaprogramming toolchain, where a clean environment is worth the setup cost.

After that you generate a development project with CMake in the usual way:

```bash
mkdir build && cd build
cmake ..
```

and then build and run all the tests with:

```bash
make check
```

The repository top level shows the structure of the project: the lager/ directory with the headers, doc/, example/, test/, tools/, a resources/ directory, cmake/ and nix/ directories, plus BUILD and WORKSPACE files for the Bazel build systems, a default.nix and flake.nix for Nix, and shell.nix for the development environment.

## The examples and what they show

The README points at an architecture overview for a guided tour with code samples, and then lists the other examples in the repository.

The counter example is described as a minimalistic one with multiple UIs, which is the interesting one for judging whether the architecture is worth adopting. Multiple UIs over the same state is exactly the case that exposes the weakness of a callback-heavy design, because the same store has to drive two independent views.

The autopong example is a basic game using SDL2, and the snake example is also in the repository. The example directory has its own CMakeLists.txt and the four example folders are counter, autopong, snake and todo.

The most telling example is not in this repository at all. Ewig is a terminal text editor with undo, asynchronous loading and more, and it lives in a separate repository under the same author. An editor with undo and asynchronous loading is the honest test of a value-oriented architecture, because undo needs a snapshot of state and asynchronous loading means the state changes at a moment you did not choose. If the architecture could not handle that, it would not be worth much for interactive software.

The documentation directory in the repository and the site at sinusoid.es/lager hold the architecture overview and the reference pages, and the README also links to the Redux and Elm introductions for readers who know the JavaScript or web versions of the pattern and want to see the mapping onto C++.

## What it costs and what to compare it with

The honest cost of this approach in C++ is that value semantics for a deep state tree means copying, and copying means either persistent data structures or plain copies, both of which have a price. The README links to immer, also hosted under the same GitHub account, as the reference for immutability, and persistent data structures of that kind are the natural companion to this architecture.

There is also the ergonomic cost that comes with rewriting callbacks as dispatched actions. Every interaction that previously called a member function now has to be expressed as an action that the reducer handles, and any side effect that previously happened inline has to become an effect procedure that the runtime invokes. That is a real refactor, not an incremental addition, and it is why the examples and the architecture overview matter more than the API reference.

The alternatives to compare against are worth naming precisely.

You could also roll your own value types with plain copies, though for deep state trees that gets expensive fast.

For the architecture half, the alternatives are the traditional object-oriented approach with signals and slots, or an event-driven design with a message bus. Both are lighter in ceremony and neither gives you state snapshots for free.

On the practical side, the project is MIT licensed, which lets you embed it in a commercial product as long as you keep the copyright notice. The last push to the repository was on 2026-07-29, and there are no GitHub releases, so the version to pin against comes from the source tree rather than a tag. The author solicits sponsorship for the long-term work, which is the signal about what kind of commitment the project has behind it.

## Conclusion

Adopt lager if you are writing an interactive C++ program with a UI and want testable pure logic, thread-safe state and undo, and you are willing to restructure callbacks into dispatched actions. Skip it if your program is a batch job, a library or a game loop with no meaningful state to serialise, since the value-oriented overhead buys nothing there. Verify first by building the examples with CMake after nix-shell, and confirm that your state type is copyable and serialisable before committing, because time travel depends on both.

## FAQ

### What is lager and what architecture does it implement?

lager is a C++17 library that assists value-oriented design by implementing unidirectional data flow, heavily inspired by Elm and Redux. It promotes simple value types and testable application logic built from pure functions, and it provides time travel over the state.

### How do I install and build lager?

The library is header only, so you can copy the lager subfolder into your include path, or install it with CMake by creating a build directory, running cmake .. and sudo make install. For development the README recommends nix-shell at the repository root to get an isolated environment, then cmake .. in a build directory and make check to run the tests.

### What does lager depend on?

It depends on Zug and Boost Hana, and it needs C++17 with a compliant compiler and standard library. The README says it is continuously tested with GCC 7 and might work with other compilers. Optional extensions and modules may have their own dependencies.

### How does lager make concurrent code easier?

Because the state is immutable and held by value, threads can work on their own local copies without mutexes or other synchronisation, and they communicate results back to the main thread by dispatching actions rather than by mutating shared state.

### What examples exist for lager?

The repository ships counter, a minimal example with multiple UIs, autopong, a basic SDL2 game, snake and todo, and the README also points to Ewig, a separate terminal text editor with undo and asynchronous loading that uses the library.

## Sources

- [arximboldi/lager on GitHub](https://github.com/arximboldi/lager)
- [Issues](https://github.com/arximboldi/lager/issues)
- [License: MIT](https://github.com/arximboldi/lager/blob/master/LICENSE)
- [Project website](https://sinusoid.es/lager/)
- [README](https://github.com/arximboldi/lager/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/arximboldi-lager
