DEV Community

skillman1337
skillman1337

Posted on

What Happens When You Port a 2001 C++ Library to C++20

Andrei Alexandrescu's Loki library shipped in 2001 alongside his book Modern C++ Design. It was groundbreaking — policy-based design, typelists, smart pointers, abstract factories, visitors, all built with template metaprogramming that pushed C++98 to its absolute limit.

Twenty-five years later, I rewrote the whole thing in C++20.

Repo: github.com/skillman1337/modern-loki


Why bother?

The original Loki was held together with recursive template inheritance and prayer. It predates variadic templates, constexpr, concepts, std::variant, std::tuple, and standard threading. Every pattern in the book can be expressed more clearly with modern C++.

I wanted to see what Modern C++ Design looks like when you actually have modern C++.


What changed

2001 (original Loki) 2025 (Modern Loki) C++20 feature
Recursive Typelist<H, T> Variadic typelist<Ts...> Parameter packs, fold expressions
SingletonHolder with OS locks singleton_holder with DCLP std::atomic, policy-based threading
Manual SmartPtr policies smart_ptr with move semantics Move constructors, operator<=>
Functor with virtual dispatch functor wrapping std::function std::function, concepts
AbstractFactory with virtual MI Tuple-based abstract_factory std::tuple, fold expressions
GenScatterHierarchy (recursive) Flat variadic inheritance public Unit<Ts>... pack expansion
Windows-only threading Portable threading policies std::mutex, std::shared_mutex
Custom Tuple Deleted Just use std::tuple

The result is header-only, zero dependencies, and compiles on MSVC 2022, GCC 13+, and Clang 17+.


The part I'm most proud of: documentation that compiles

Every component has its own Markdown file with an API table and 10 real-world code examples. That's 150 examples total.

Here's the thing: most C++ library docs are written by someone who last tested the examples three compiler versions ago. I didn't want that.

So I wrote a Python script that:

  1. Extracts every cpp code block from every .md file
  2. Compiles each one against the library headers
  3. Runs the resulting executable
  4. Fails CI if anything doesn't compile or crashes

This runs in GitHub Actions on every push. If the docs lie, the build fails.

# From scripts/test_docs.py — the core loop
for i, (title, code) in enumerate(examples):
    tag = f'{base}_{i}'
    status, output = test_one(tag, code)
    marker = 'PASS' if status == 'OK' else 'FAIL'
    print(f'  [{done:3d}/{total}] {marker}  {basename}#{i+1} {title}')
Enter fullscreen mode Exit fullscreen mode

Here's what a run looks like:

Compiler : msvc (cl.exe)
Examples : 150 across 15 files

  [  1/150] PASS  abstract_factory.md#1 Basic widget factory  (2.6s)
  [  2/150] PASS  abstract_factory.md#2 Cross-platform UI factory  (2.8s)
  ...
  [150/150] PASS  loki.md#10 Clone factory with type_traits  (3.0s)

=== TOTAL: 150/150 passed  (448s) ===
Enter fullscreen mode Exit fullscreen mode

I've never seen another C++ library do this. If yours does, please tell me — I want to steal your approach.


Example: before and after

Here's what a typelist looked like in 2001:

// Original Loki (C++98)
typedef TYPELIST_4(int, double, char, std::string) MyTypes;
// Macro expands to:
// Typelist<int, Typelist<double, Typelist<char,
//     Typelist<std::string, NullType>>>>
Enter fullscreen mode Exit fullscreen mode

Here's the same thing today:

// Modern Loki (C++20)
using MyTypes = loki::typelist<int, double, char, std::string>;

static_assert(loki::tl::length_v<MyTypes> == 4);
static_assert(loki::tl::contains_v<MyTypes, double>);
static_assert(loki::tl::index_of_v<MyTypes, char> == 2);
Enter fullscreen mode Exit fullscreen mode

No macros. No recursive inheritance. Just a variadic template and constexpr metafunctions.


Example: policy-based singleton

#include <loki/singleton.hpp>
#include <iostream>
#include <string>

struct AppConfig {
    std::string db_host = "localhost";
    int db_port = 5432;
};

// Thread-safe, DCLP, default lifetime
using Config = loki::singleton_holder<
    AppConfig,
    loki::create_using_new,
    loki::default_lifetime,
    loki::class_level_lockable
>;

// Anywhere in your code:
auto& cfg = Config::instance();
std::cout << cfg.db_host << ":" << cfg.db_port << "\n";
Enter fullscreen mode Exit fullscreen mode

You pick your creation policy, lifetime policy, and threading policy independently. That's the whole point of Alexandrescu's design — and it reads a lot better in C++20 than it did with IMPLEMENT_DEFAULT_SINGLETON macros.


Is this useful in production?

Honestly? For most of it, no. Use std::unique_ptr over smart_ptr. Use std::flat_map (C++23) over assoc_vector. Use a real async runtime over the threading policies.

The README says this upfront. This is a teaching and reference library. It's for:

  • Studying the patterns from Modern C++ Design in modern idioms
  • Teaching template metaprogramming and policy-based design
  • Using components that std:: doesn't provide (abstract factories, multi-methods, small object allocators)

Try it

git clone https://github.com/skillman1337/modern-loki.git
cd modern-loki
cmake -B build
cmake --build build --config Release
ctest --test-dir build -C Release --output-on-failure
Enter fullscreen mode Exit fullscreen mode

Or just read the docs — every example compiles. I made sure.


If you've read Modern C++ Design, I'd love to hear which patterns you think aged well and which didn't. Drop a comment.

Top comments (0)