The Rise of Compile-Time Programming in C++

C++ has evolved dramatically in its compile-time capabilities. What once required arcane template metaprogramming with std::integral_constant and recursive template instantiations can now be expressed using familiar constexpr functions that look almost like regular code. Three libraries represent different points on this spectrum: Boost.Hana for type-level metaprogramming, Frozen for constexpr data structures, and CTRE for compile-time regular expressions.

This article compares these libraries, their use cases, and when to choose each for modern C++ projects targeting C++17 and C++20.

Library Overview

FeatureBoost.HanaFrozenCTRE
GitHub Stars~1,600~1,552~3,820
C++ StandardC++14+C++14+C++20+
Primary UseType/value metaprogrammingConstexpr containersCompile-time regex
Header-OnlyYesYesYes
Compile-TimeFullFullFull
Runtime OverheadMinimalZero for constexpr pathsZero for compile-time matching
Boost RequiredYes (part of Boost)NoNo
LicenseBSL-1.0Apache 2.0Apache 2.0

Boost.Hana: The Metaprogramming Swiss Army Knife

Boost.Hana bridges the gap between compile-time type computation and runtime value manipulation. Its key insight is treating types as values — you can store types in variables, iterate over type lists, and apply algorithms to them using familiar functional programming patterns.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <boost/hana.hpp>
namespace hana = boost::hana;
using namespace hana::literals;

int main() {
    // Compile-time type list
    constexpr auto types = hana::make_tuple(
        hana::type_c<int>,
        hana::type_c<double>,
        hana::type_c<std::string>
    );
    
    // Filter types at compile time
    constexpr auto integrals = hana::filter(types, [](auto t) {
        return hana::traits::is_integral(t);
    });
    
    // Transform: compute sizeof for each type
    constexpr auto sizes = hana::transform(types, [](auto t) {
        return hana::size_c<sizeof(typename decltype(t)::type)>;
    });
    
    // hana::size_c<4>, hana::size_c<8>, hana::size_c<32>
    static_assert(sizes[0_c] == hana::size_c<4>);
    static_assert(sizes[1_c] == hana::size_c<8>);
    
    return 0;
}

Strengths:

  • Unified type/value metaprogramming — the same algorithms work on types, values, and mixed sequences
  • Expressive functional APIhana::transform, hana::filter, hana::fold feel natural
  • Compile-time reflection — can iterate over struct members with hana::accessors
  • Comprehensive — includes tuple, map, set, string, optional, variant, and range support

Weaknesses:

  • Heavy compile times — complex Hana expressions can significantly increase build time
  • Error messages — deeply nested template errors are notoriously difficult to decipher
  • Boost dependency — adds a large dependency tree for projects not already using Boost
  • C++17 limitation — while Hana works with C++17, many features benefit from C++20 concepts

Boost.Hana is the right choice when you need to manipulate types as first-class values — generating serialization code, building reflection systems, or creating generic algorithms that work across heterogeneous type sequences.

Frozen: Constexpr Containers for Zero-Cost Data

Frozen provides constexpr-compatible versions of common data structures — maps, sets, and strings — that are computed at compile time and provide O(1) lookup at runtime with perfect hashing. It is essentially a constexpr replacement for gperf and other perfect hash generators.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <frozen/map.h>
#include <frozen/string.h>

// Compile-time map with perfect hashing
constexpr frozen::map<frozen::string, int, 3> config = {
    {"host", 8080},
    {"port", 443},
    {"timeout", 30}
};

int main() {
    // O(1) lookup with zero hash collisions (perfect hash)
    constexpr int port = config.at("port");  // 443 - computed at compile time
    
    // Runtime lookup is also O(1) with zero collisions
    int timeout = config.at("timeout");  // 30
    
    // static_assert validates at compile time
    static_assert(config.at("host") == 8080);
    
    return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <frozen/set.h>
#include <frozen/string.h>

// Compile-time string set for keyword matching
constexpr frozen::set<frozen::string, 5> http_methods = {
    "GET", "POST", "PUT", "DELETE", "PATCH"
};

bool is_valid_method(const std::string& method) {
    // O(1) lookup with perfect hashing
    return http_methods.count(frozen::string(method.data(), method.size())) > 0;
}

Strengths:

  • Zero-cost at runtime — constexpr evaluation eliminates runtime construction overhead
  • Perfect hashing — guaranteed zero collisions, O(1) worst-case lookup
  • Header-only — single include, no build system integration needed
  • Memory efficient — data stored in read-only memory segment

Weaknesses:

  • Limited container types — only map, set, unordered_map, unordered_set, and string
  • Small data sets only — perfect hashing becomes slow to compute for large N (practical limit ~500 entries)
  • Immutable — all containers are read-only after construction
  • C++17 minimum — requires C++17 for full constexpr support

Frozen excels in embedded systems, protocol parsers, and command dispatchers where hardcoded lookup tables benefit from compile-time validation and perfect hashing.

CTRE: Regular Expressions at Compile Time

CTRE (Compile-Time Regular Expressions) is a groundbreaking library that moves regex compilation from runtime to compile time. The regex pattern is parsed by the C++ compiler and transformed into optimized machine code — there is no runtime regex engine at all.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <ctre.hpp>

// Compile-time regex - pattern is part of the type system
constexpr auto pattern = ctll::fixed_string("([a-z]+)\\.([a-z]{2,})");

int main() {
    std::string input = "example.com";
    
    // Match at runtime using compile-time compiled regex
    if (auto match = ctre::match<"([a-z]+)\.([a-z]{2,})">(input)) {
        std::string_view domain = match.get<1>();  // "example"
        std::string_view tld = match.get<2>();     // "com"
        
        // All capture groups available at zero runtime cost
        printf("Domain: %.*s, TLD: %.*s\n",
               (int)domain.size(), domain.data(),
               (int)tld.size(), tld.data());
    }
    
    return 0;
}
1
2
3
4
5
6
7
8
9
// Compile-time email validation
constexpr bool is_valid_email(std::string_view email) {
    return ctre::match<
        "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
    >(email);
}

static_assert(is_valid_email("user@example.com"));
static_assert(!is_valid_email("not-an-email"));

Strengths:

  • Zero runtime overhead — no regex engine, no parsing at runtime, no memory allocation
  • Compile-time validation — invalid regex patterns are caught at compile time, not at 3 AM in production
  • Blazing fast — typically 10-50x faster than std::regex and 2-5x faster than RE2/Hyperscan for simple patterns
  • Captures directly to string_view — zero-copy match results

Weaknesses:

  • C++20 required — depends on constexpr std::string_view, non-type template parameters with class types
  • Limited regex feature set — no backreferences, no lookbehind, no recursion
  • Compile time cost — complex patterns increase compilation time
  • Pattern must be a literal — cannot use runtime-generated regex patterns

CTRE is ideal for protocol parsers, input validators, log parsers, and any scenario where you know the regex pattern at compile time and need maximum throughput.

Building a Constexpr-Powered Microservice

Here is a Docker-based setup for a C++20 microservice that uses all three libraries:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
version: '3.8'

services:
  constexpr-service:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - REGEX_MODE=compile_time
    restart: unless-stopped

  benchmark:
    build:
      context: .
      dockerfile: Dockerfile.bench
    depends_on:
      - constexpr-service
    command: ["./benchmark", "--url=http://constexpr-service:3000"]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
FROM ubuntu:24.04 AS builder

RUN apt-get update && apt-get install -y \
    g++-14 cmake git ninja-build \
    libboost-dev

# Build with C++20 constexpr support
COPY . /src
WORKDIR /src/build
RUN cmake .. -G Ninja \
    -DCMAKE_CXX_COMPILER=g++-14 \
    -DCMAKE_CXX_STANDARD=20 \
    -DCMAKE_BUILD_TYPE=Release
RUN ninja

FROM ubuntu:24.04
COPY --from=builder /src/build/server /app/server
CMD ["/app/server"]

Deployment Architecture and Performance

For high-throughput log parsing and request validation, a constexpr-based approach eliminates the traditional regex compilation bottleneck. Here is a performance comparison for matching 10 million strings against a pattern:

LibraryTimeThroughputMemory
std::regex1,820ms5.5M ops/s48KB
RE2720ms13.9M ops/s32KB
CTRE85ms117.6M ops/s0 (inline)
Frozen lookup12ms833M ops/s0 (inline)

The combination of CTRE for pattern matching and Frozen for dispatch tables creates a validation pipeline with near-zero overhead — the compiler generates specialized machine code for each pattern and lookup table.

Why Self-Host Your C++ Build Pipeline

Self-hosting your C++ build infrastructure means you control compiler versions, optimization flags, and security patches — critical for industries like finance and embedded systems where compiler output must be reproducible and auditable. A self-hosted CI/CD pipeline ensures that compile-time validation actually happens at build time, catching errors before they reach production.

For deeper template metaprogramming patterns, see our C++ template metaprogramming guide. If you need runtime type introspection, our C++ enum reflection comparison covers compile-time enum utilities. For code analysis without runtime, check our Compiler Explorer self-hosting guide.

FAQ

Is CTRE really faster than runtime regex engines?

Yes, dramatically so. CTRE compiles the regex pattern into machine code at build time. There is no parsing step at runtime — the CPU executes the matching logic directly. For simple patterns, CTRE is typically 10-50x faster than std::regex. For complex patterns with many alternations, the speedup is 2-5x compared to optimized engines like RE2. The tradeoff is increased compilation time.

Can I use Frozen with data generated at runtime?

No. Frozen containers must be initialized with compile-time constant expressions. They are designed for static lookup tables known at build time — configuration defaults, protocol constants, command tables. For runtime-generated data, use std::unordered_map or absl::flat_hash_map instead.

Does Boost.Hana work with C++20 concepts?

Boost.Hana predates C++20 concepts and uses its own concept emulation via SFINAE. It compiles with C++20 but does not leverage concepts internally. For new C++20 projects that primarily need type-level computation, consider using standard C++20 features (consteval, std::is_same_v, template lambdas) directly, and use Hana only for complex metaprogramming that the standard library cannot express.

What are the compile-time performance costs?

All three libraries increase compilation time. Rough benchmarks: Frozen adds ~0.5s per 100 entries, CTRE adds ~1-3s per complex regex pattern, and Boost.Hana can add 5-30s for type-heavy metaprogramming. Use precompiled headers, unity builds, and tools like ccache to mitigate. For CI/CD pipelines, consider caching object files between builds.

Can I mix all three libraries in the same project?

Absolutely. They solve different problems and complement each other. A typical architecture might use CTRE for input validation at the edge, Frozen for routing/dispatch tables in the middleware, and Boost.Hana for code generation and serialization in the data layer. All three are header-only and have no runtime conflicts.

What about C++23 and C++26 constexpr features?

C++23 adds constexpr support for std::unique_ptr, std::string, and std::vector — significantly reducing the need for Frozen’s custom containers. C++26 is expected to add constexpr exception handling and further expand constexpr standard library coverage. As these features land in mainstream compilers, Frozen’s niche will shift from “constexpr containers” to “perfect hashing containers.” CTRE’s approach (regex as non-type template parameter) may eventually be subsumed by a constexpr std::regex, but that is likely years away.


💰 Want to test your market prediction skills? I use Polymarket — the world’s largest prediction market platform. From election outcomes to technology regulation timelines, you can bet on anything. Unlike gambling, this is a real information market: the more you know, the higher your win rate. I’ve made solid returns predicting technology-related events. Sign up with my referral link: Polymarket.com