Project 12: Template Metaprogramming Library
Build a compile-time computation library with type lists, traits, and zero-runtime-cost abstractions
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Master |
| Time Estimate | 1-2 months |
| Language | C++ (C++17/20/23) |
| Prerequisites | All previous projects, template basics |
| Key Topics | Variadic templates, SFINAE, concepts, constexpr, type lists, TMP |
1. Learning Objectives
By completing this project, you will:
- Master variadic templates - Work with parameter packs, fold expressions, and recursive instantiation
- Understand SFINAE deeply - Control overload resolution through substitution failure
- Use C++20 concepts - Write cleaner template constraints
- Implement type lists - Store, query, and transform types at compile time
- Build compile-time strings - Manipulate strings as template parameters
- Create zero-cost abstractions - Everything computed at compile time, nothing at runtime
- Understand type traits - Query and transform type properties
- Apply constexpr/consteval - Force compile-time evaluation
2. Theoretical Foundation
2.1 Core Concepts
Template metaprogramming (TMP) is programming where the compiler is the runtime. Instead of computing values when your program runs, TMP computes types and values when your program compiles. The result: zero runtime cost.
The Metaprogramming Paradigm:
Traditional Programming Template Metaprogramming
┌─────────────────────────┐ ┌─────────────────────────┐
│ Runtime │ │ Compile Time │
│ │ │ │
│ • Variables hold values │ │ • Types ARE values │
│ • Functions transform │ │ • Templates ARE funcs │
│ values │ │ • Instantiation IS │
│ • Loops iterate │ │ evaluation │
│ • Conditions branch │ │ • Specialization IS │
│ │ │ pattern matching │
│ • Computed when running │ │ • Computed when │
│ │ │ compiling │
└─────────────────────────┘ └─────────────────────────┘
Key Insight: Templates are a Turing-complete functional language embedded in C++. You can compute anything at compile time that you could compute at runtime.
2.2 Why This Matters
Template metaprogramming enables:
- Zero-cost abstractions - High-level code that compiles to optimal machine code
- Type-safe APIs - Catch errors at compile time, not runtime
- Performance-critical libraries - Eigen, Boost, STL all use TMP heavily
- Domain-specific languages - Express patterns in types (Spirit, Hana)
- Static reflection - Query types at compile time before C++26 reflection
Real-world usage:
std::tuple,std::variant,std::optional- Type manipulation- Eigen matrix library - Expression templates for optimal linear algebra
- Boost.Spirit - Parser combinators expressed in types
- Boost.Hana - Metaprogramming with value semantics
- JSON libraries - Compile-time JSON parsing (simdjson concepts)
2.3 Historical Context
1990: Templates introduced in C++
└── Just for generic containers
1994: Erwin Unruh's prime number program
└── First demonstration: compute primes during compilation
└── Compiler error messages contained the primes!
1998: STL standardized
└── Heavy use of template techniques
2003: Alexandrescu's "Modern C++ Design"
└── Formalized TMP as a paradigm
└── Policy-based design, type lists, traits
2011: C++11 - variadic templates, constexpr
└── Much cleaner TMP syntax
2014-20: constexpr expansion, concepts
└── constexpr if, constexpr virtual, concepts
└── TMP becomes mainstream
2023: Deducing this, more constexpr
└── Easier recursive lambdas
└── constexpr containers
Future: Static reflection (C++26?)
└── Will replace much of TMP
2.4 Common Misconceptions
- “TMP is just for library authors”
- Reality: Understanding TMP helps you use STL/Boost effectively and debug template errors
- “TMP makes code slower”
- Reality: TMP has zero runtime cost—it moves computation to compile time
- “TMP is obsolete with constexpr”
- Reality: constexpr computes values; TMP manipulates types. Both are needed
- “TMP produces unreadable code”
- Reality: Modern C++ (concepts, if constexpr) makes TMP much cleaner
- “Template errors are impossible to read”
- Reality: Concepts produce clear error messages; understanding TMP helps debug the rest
3. Project Specification
3.1 What You Will Build
A template metaprogramming library that provides:
- Type Lists -
TypeList<int, double, string>with operations likeAt,Length,Contains,Filter,Transform - Type Traits - Extended traits beyond
<type_traits> - Compile-Time Strings -
FixedStringthat can be template parameters - Compile-Time JSON Parser - Parse JSON literals at compile time
- Type-Safe Printf - Format strings checked at compile time
- Value Lists -
ValueList<1, 2, 3>with arithmetic operations
3.2 Functional Requirements
Type List Operations:
Length<List>- Number of types in listAt<List, Index>- Type at indexContains<List, T>- Check if T is in listIndexOf<List, T>- Find index of TPushFront<List, T>/PushBack<List, T>- Add typesPopFront<List>/PopBack<List>- Remove typesConcat<List1, List2>- Concatenate listsFilter<List, Predicate>- Keep types matching predicateTransform<List, MetaFunction>- Apply function to each typeUnique<List>- Remove duplicate typesReverse<List>- Reverse type order
Compile-Time Strings:
- Construction from string literals
- Concatenation
- Substring extraction
- Comparison
- Character iteration (for compile-time parsing)
Compile-Time JSON:
- Parse JSON literals
- Access object properties
- Access array elements
- Type-safe value extraction
3.3 Non-Functional Requirements
- Compilation: Must work with C++17 (some features C++20)
- Zero Runtime Cost: All computation at compile time
- Clear Error Messages: Use concepts/static_assert for helpful errors
- Header-Only: No runtime library needed
- Standards Compliant: No compiler extensions
3.4 Example Usage / Output
// Type list operations
using MyTypes = TypeList<int, double, std::string>;
static_assert(Length<MyTypes>::value == 3);
static_assert(std::is_same_v<At<MyTypes, 0>, int>);
static_assert(Contains<MyTypes, double>::value);
static_assert(IndexOf<MyTypes, std::string>::value == 2);
using Filtered = Filter<MyTypes, std::is_arithmetic>; // TypeList<int, double>
using Transformed = Transform<MyTypes, std::add_pointer>; // TypeList<int*, double*, std::string*>
// Compile-time string
constexpr FixedString hello = "Hello";
constexpr FixedString world = "World";
constexpr auto greeting = hello + ", " + world + "!";
static_assert(greeting == "Hello, World!");
// Compile-time JSON parsing
constexpr auto json = R"({"name": "Alice", "age": 30})"_json;
static_assert(json["name"] == "Alice");
static_assert(json["age"] == 30);
// Type-safe printf format checking
print<"Hello, %s! You are %d years old.">("Alice", 30); // OK
// print<"Hello, %s!">(42); // Compile error: expected string, got int
// Reflection-like serialization
struct Person {
std::string name;
int age;
REFLECT(name, age)
};
constexpr auto json_str = to_json(Person{"Bob", 25});
// json_str is computed at compile time!
3.5 Real World Outcome
// After implementing your library, this code compiles:
#include "meta/typelist.hpp"
#include "meta/fixed_string.hpp"
#include "meta/compile_time_json.hpp"
// 1. Type list demonstration
using Numbers = TypeList<int, long, float, double>;
using Strings = TypeList<std::string, std::string_view, const char*>;
// Filter to only integral types
using Integrals = Filter<Numbers, std::is_integral>;
static_assert(std::is_same_v<Integrals, TypeList<int, long>>);
// Transform to add const
using ConstNumbers = Transform<Numbers, std::add_const>;
static_assert(std::is_same_v<At<ConstNumbers, 0>, const int>);
// Concatenate and remove duplicates
using All = Concat<Numbers, TypeList<int, char>>;
using UniqueAll = Unique<All>;
// 2. Compile-time string manipulation
constexpr FixedString path = "/api/v1/users";
constexpr auto segments = split<path, '/'>(); // ["", "api", "v1", "users"]
static_assert(segments[1] == "api");
// Format strings with compile-time validation
constexpr auto fmt = FixedString{"User: %s (age %d)"};
auto message = format<fmt>("Alice", 30); // OK
// auto bad = format<fmt>(42, "Alice"); // ERROR: wrong types
// 3. Compile-time JSON
constexpr auto config = R"({
"server": {
"host": "localhost",
"port": 8080
},
"features": ["logging", "metrics"]
})"_json;
static_assert(config["server"]["host"] == "localhost");
static_assert(config["server"]["port"] == 8080);
static_assert(config["features"][0] == "logging");
// 4. Type-indexed tuple variant
using Variant = TypeMap<
Pair<int, std::string>,
Pair<double, std::vector<int>>,
Pair<char, std::array<int, 10>>
>;
static_assert(std::is_same_v<Get<Variant, int>, std::string>);
// 5. Compile-time reflection helper
struct Point {
double x, y, z;
META_REFLECT(x, y, z)
};
static_assert(field_count<Point> == 3);
static_assert(field_name<Point, 0> == "x");
// All of the above: ZERO runtime cost
int main() {
std::cout << "All compile-time assertions passed!" << std::endl;
return 0;
}
Compilation output (errors when misused):
$ g++ -std=c++20 example.cpp
# If you make a mistake like:
# format<"Hello %s">(42)
example.cpp:45:12: error: static assertion failed
45 | static_assert(is_same_v<ArgType, const char*>,
| ^~~~~~~~~~~~~
note: Format specifier '%s' requires string type, but got 'int'
4. Solution Architecture
4.1 High-Level Design
Template Metaprogramming Library Architecture
═══════════════════════════════════════════════════════════════════
Layer 4: User-Facing APIs
┌─────────────────────────────────────────────────────────────────┐
│ format<"...">() json["key"] TypeList<Ts...> │
│ REFLECT(...) for_each_type static_assert checks │
└─────────────────────────────────────────────────────────────────┘
│
▼
Layer 3: High-Level Metafunctions
┌─────────────────────────────────────────────────────────────────┐
│ Filter<List, Pred> Transform<List, F> Unique<List> │
│ Split<String> ParseJSON<String> FormatCheck<...> │
└─────────────────────────────────────────────────────────────────┘
│
▼
Layer 2: Core Metafunctions
┌─────────────────────────────────────────────────────────────────┐
│ At<List, I> PushBack<List, T> Concat<L1, L2> │
│ Length<List> Contains<List, T> IndexOf<List, T> │
└─────────────────────────────────────────────────────────────────┘
│
▼
Layer 1: Primitives
┌─────────────────────────────────────────────────────────────────┐
│ TypeList<Ts...> FixedString<N> ValueList<Vs...> │
│ Pair<K, V> Identity<T> Constant<V> │
└─────────────────────────────────────────────────────────────────┘
│
▼
Layer 0: Language Features
┌─────────────────────────────────────────────────────────────────┐
│ Template parameters constexpr static_assert │
│ Variadic templates fold expressions if constexpr │
│ SFINAE / concepts NTTP (C++20) Deducing this │
└─────────────────────────────────────────────────────────────────┘
4.2 Key Components
┌──────────────────────────────────────────────────────────────┐
│ COMPONENT RELATIONSHIPS │
└──────────────────────────────────────────────────────────────┘
TypeList<Ts...> FixedString<N>
│ │
├── Length<L> ├── size()
├── At<L, I> ├── operator[]
├── Contains<L, T> ├── operator+
├── IndexOf<L, T> ├── operator==
│ └── substr<Start, Len>
│ │
├── PushFront<L, T> │
├── PushBack<L, T> ▼
├── PopFront<L> ┌─────────────────┐
├── PopBack<L> │ JSON Parser │
│ │ │
├── Concat<L1, L2> │ parseValue() │
├── Reverse<L> │ parseObject() │
├── Unique<L> │ parseArray() │
│ │ parseString() │
└── Higher-Order │ parseNumber() │
├── Filter<L, Pred> └─────────────────┘
├── Transform<L, F>
├── Fold<L, F, Init>
└── ForEach<L, F>
Uses Uses
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Type Traits │ │ Format Check │
│ │ │ │
│ is_type_list │ │ parse specs │
│ is_complete │ │ match types │
│ has_member_x │ │ emit errors │
└─────────────────┘ └─────────────────┘
4.3 Data Structures
TypeList:
template<typename... Ts>
struct TypeList {
static constexpr size_t size = sizeof...(Ts);
};
FixedString (Non-Type Template Parameter):
template<size_t N>
struct FixedString {
char data[N];
constexpr FixedString(const char (&str)[N]);
constexpr size_t size() const { return N - 1; }
constexpr char operator[](size_t i) const { return data[i]; }
};
ValueList:
template<auto... Vs>
struct ValueList {
static constexpr size_t size = sizeof...(Vs);
};
4.4 Algorithm Overview
Recursive Type Operations:
At<TypeList<A, B, C>, 1>
│
├── Match: At<TypeList<Head, Tail...>, 0> → Head
│
└── Recurse: At<TypeList<Head, Tail...>, N>
→ At<TypeList<Tail...>, N-1>
So: At<TypeList<A, B, C>, 1>
→ At<TypeList<B, C>, 0>
→ B
Filter with Predicate:
Filter<TypeList<int, float, double>, is_integral>
│
├── For each T in list:
│ └── if Pred<T>::value → include T
│
└── Concat results
So: Filter<TypeList<int, float, double>, is_integral>
→ TypeList<int>
5. Implementation Guide
5.1 Development Environment Setup
# Required: C++17 minimum, C++20 recommended
g++ --version # Need GCC 10+ or Clang 10+
# Project structure
mkdir -p template_meta/include/meta
mkdir -p template_meta/tests
# CMakeLists.txt
cat > template_meta/CMakeLists.txt << 'EOF'
cmake_minimum_required(VERSION 3.20)
project(template_meta CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(tests tests/main.cpp)
target_include_directories(tests PRIVATE include)
EOF
5.2 Project Structure
template_meta/
├── CMakeLists.txt
├── include/
│ └── meta/
│ ├── typelist.hpp # TypeList and operations
│ ├── valuelist.hpp # ValueList and operations
│ ├── traits.hpp # Extended type traits
│ ├── fixed_string.hpp # Compile-time strings
│ ├── json.hpp # Compile-time JSON
│ ├── format.hpp # Type-safe format strings
│ └── meta.hpp # All-in-one include
└── tests/
├── main.cpp
├── test_typelist.cpp
├── test_string.cpp
└── test_json.cpp
5.3 The Core Question You’re Answering
“How can I compute with types instead of values, and perform all computation before my program ever runs?”
This is about understanding that:
- Types can be values in a different dimension
- Templates are functions that take types and return types
- Specialization is pattern matching
- The compiler is your runtime
5.4 Concepts You Must Understand First
| Concept | Self-Assessment Question | Resource |
|---|---|---|
| Variadic templates | Can you write a function taking any number of arguments? | “C++ Templates” Ch. 4 |
| Parameter packs | What does sizeof...(Ts) return? |
cppreference |
| Template specialization | How do you specialize for a specific type? | “C++ Templates” Ch. 2 |
| SFINAE | Why does enable_if work? |
“C++ Templates” Ch. 8 |
| Concepts (C++20) | How do you constrain a template? | “C++20” by Josuttis |
| constexpr | What can be computed at compile time? | “EMC++” Item 15 |
| Fold expressions | How do you sum a parameter pack? | cppreference |
5.5 Questions to Guide Your Design
Type List Design:
- How do you store an arbitrary number of types?
- How do you access the Nth type (recursive vs fold)?
- How do you express “type → type” transformations?
- How do you handle empty lists?
Compile-Time Computation:
- What makes a function
constexpr-eligible? - How do you force compile-time evaluation with
consteval? - How do you iterate characters in a string at compile time?
- How do you accumulate results (fold expressions)?
Error Messages:
- How do you make template errors readable?
- How do you use
static_asserteffectively? - How do concepts improve error messages?
5.6 Thinking Exercise
Before coding, trace through this mentally:
template<typename... Ts> struct TypeList {};
template<typename List> struct Length;
template<typename... Ts>
struct Length<TypeList<Ts...>> {
static constexpr size_t value = sizeof...(Ts);
};
template<typename List, size_t I> struct At;
template<typename Head, typename... Tail>
struct At<TypeList<Head, Tail...>, 0> {
using type = Head;
};
template<typename Head, typename... Tail, size_t I>
struct At<TypeList<Head, Tail...>, I> {
using type = typename At<TypeList<Tail...>, I-1>::type;
};
// What is At<TypeList<int, double, char>, 2>::type?
Trace the instantiation chain:
At<TypeList<int, double, char>, 2>- → matches partial specialization with I (I != 0)
- →
At<TypeList<double, char>, 1>::type - → matches partial specialization with I (I != 0)
- →
At<TypeList<char>, 0>::type - → matches specialization with I = 0
- →
type = char
5.7 Hints in Layers
Hint 1: Start with TypeList basics
Begin with just TypeList<Ts...> and Length<List>.
Get static_assert(Length<TypeList<int, double>>::value == 2) working.
This proves your recursive pattern works.
Hint 2: Pattern matching with specialization
The key insight: partial specialization = pattern matching.
template<typename Head, typename... Tail>
struct At<TypeList<Head, Tail...>, 0> { using type = Head; };
This ONLY matches when the second parameter is 0.
The general case handles I > 0 by recursing.
Hint 3: Higher-order metafunctions
Filter needs to call a predicate on each type:
template<template<typename> class Pred> // <- template template parameter
Transform needs to apply a metafunction:
template<template<typename> class F>
These take "metafunctions" (template templates) as parameters.
Hint 4: FixedString as NTTP
C++20 allows class types as non-type template parameters if they're
"structural" (all public members, literal type).
template<size_t N>
struct FixedString {
char data[N];
constexpr FixedString(const char (&str)[N]) {
for (size_t i = 0; i < N; i++) data[i] = str[i];
}
};
// Deduction guide
template<size_t N>
FixedString(const char (&)[N]) -> FixedString<N>;
// Now use as template parameter:
template<FixedString S>
void func() { /* S.data is the string */ }
5.8 The Interview Questions They’ll Ask
- “Explain SFINAE and give an example.”
- Expected: Substitution Failure Is Not An Error. When template substitution fails, that overload is removed from consideration instead of being an error. Example:
enable_if.
- Expected: Substitution Failure Is Not An Error. When template substitution fails, that overload is removed from consideration instead of being an error. Example:
- “What’s the difference between
constexprandconsteval?”- Expected:
constexprcan be evaluated at compile time if possible.constevalmust be evaluated at compile time.
- Expected:
- “How would you implement a compile-time type list?”
- Expected: Variadic template class
TypeList<Ts...>, with operations implemented via partial specialization to extract head/tail.
- Expected: Variadic template class
- “What are concepts and how do they improve on SFINAE?”
- Expected: Concepts are named constraints on template parameters. They produce clearer error messages and are easier to write than SFINAE tricks.
- “What is the difference between a template and a metafunction?”
- Expected: A metafunction is a template that represents a type → type or type → value mapping. It has a standard interface (
::typeor::value).
- Expected: A metafunction is a template that represents a type → type or type → value mapping. It has a standard interface (
- “How would you detect if a type has a member function
foo()?”- Expected: Use
decltypeand SFINAE:decltype(std::declval<T>().foo()). Or with concepts:requires(T t) { t.foo(); }.
- Expected: Use
- “What are fold expressions?”
- Expected: C++17 feature to reduce a parameter pack with an operator:
(... + args)sums all args.
- Expected: C++17 feature to reduce a parameter pack with an operator:
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Variadic Templates | “C++ Templates: The Complete Guide” | Chapter 4 |
| Template Argument Deduction | “C++ Templates: The Complete Guide” | Chapter 15 |
| SFINAE | “C++ Templates: The Complete Guide” | Chapter 8 |
| Type Traits | “C++ Templates: The Complete Guide” | Chapter 19 |
| Concepts | “C++20 - The Complete Guide” | Chapter 5 |
| constexpr | “Effective Modern C++” | Item 15 |
| Expression Templates | “C++ Templates: The Complete Guide” | Chapter 27 |
| Tuples | “C++ Templates: The Complete Guide” | Chapter 25 |
5.10 Implementation Phases
Phase 1: TypeList Basics (Week 1)
// Milestone: These static_asserts pass
using L = TypeList<int, double, char>;
static_assert(Length<L>::value == 3);
static_assert(std::is_same_v<At<L, 0>, int>);
static_assert(std::is_same_v<At<L, 2>, char>);
static_assert(Contains<L, double>::value);
static_assert(!Contains<L, float>::value);
Phase 2: TypeList Transformations (Week 2)
// Milestone: Higher-order operations work
using Filtered = Filter<L, std::is_integral>;
static_assert(std::is_same_v<Filtered, TypeList<int, char>>);
using Transformed = Transform<L, std::add_pointer>;
static_assert(std::is_same_v<At<Transformed, 0>, int*>);
Phase 3: FixedString (Week 3)
// Milestone: Compile-time strings work
constexpr FixedString s1 = "Hello";
constexpr FixedString s2 = "World";
constexpr auto s3 = s1 + ", " + s2;
static_assert(s3 == "Hello, World");
static_assert(s3.size() == 12);
Phase 4: Compile-Time JSON (Weeks 4-5)
// Milestone: JSON parsing at compile time
constexpr auto j = R"({"x": 10, "y": 20})"_json;
static_assert(j["x"] == 10);
static_assert(j["y"] == 20);
Phase 5: Format Strings & Polish (Weeks 6-8)
// Milestone: Type-safe format strings
auto s = format<"Name: %s, Age: %d">("Alice", 30);
// format<"Value: %d">("wrong"); // Compile error!
5.11 Key Implementation Decisions
Type List Implementation:
// Approach 1: Recursion (traditional)
template<typename List, size_t I>
struct At {
using type = typename At<PopFront<List>, I-1>::type;
};
template<typename List>
struct At<List, 0> {
using type = typename List::Head; // or similar
};
// Approach 2: Parameter pack indexing (C++17)
template<typename... Ts, size_t I>
auto at_impl(TypeList<Ts...>, std::index_sequence<I>)
-> std::tuple_element_t<I, std::tuple<Ts...>>;
template<typename List, size_t I>
using At = decltype(at_impl(List{}, std::index_sequence<I>{}));
FixedString Concatenation:
template<size_t N1, size_t N2>
constexpr auto operator+(FixedString<N1> lhs, FixedString<N2> rhs) {
char result[N1 + N2 - 1]; // -1 for double null terminator
for (size_t i = 0; i < N1 - 1; i++) result[i] = lhs[i];
for (size_t i = 0; i < N2; i++) result[N1 - 1 + i] = rhs[i];
return FixedString<N1 + N2 - 1>(result);
}
6. Testing Strategy
Static Tests (Most Important):
// All tests are static_assert - if it compiles, it passes!
namespace tests {
// Length
static_assert(Length<TypeList<>>::value == 0);
static_assert(Length<TypeList<int>>::value == 1);
static_assert(Length<TypeList<int, double, char>>::value == 3);
// At
using L = TypeList<int, double, char>;
static_assert(std::is_same_v<At<L, 0>, int>);
static_assert(std::is_same_v<At<L, 1>, double>);
static_assert(std::is_same_v<At<L, 2>, char>);
// Contains
static_assert(Contains<L, int>::value);
static_assert(!Contains<L, float>::value);
// Transform
using Ptrs = Transform<L, std::add_pointer>;
static_assert(std::is_same_v<At<Ptrs, 0>, int*>);
// Filter
using Ints = Filter<L, std::is_integral>;
static_assert(Length<Ints>::value == 2); // int, char
// FixedString
constexpr FixedString s = "test";
static_assert(s.size() == 4);
static_assert(s[0] == 't');
static_assert(s == "test");
}
int main() {
// If we reach here, all static tests passed
std::cout << "All compile-time tests passed!\n";
}
Error Message Testing:
// Manually verify error messages are clear:
// Uncomment and check compiler output
// At<TypeList<int>, 5>::type x; // Should say "index out of bounds"
// format<"%d">("string"); // Should say "expected int, got string"
7. Common Pitfalls & Debugging
| Problem | Symptom | Root Cause | Fix |
|---|---|---|---|
| Infinite template recursion | Compiler hangs or stack overflow | Missing base case specialization | Add explicit specialization for empty list / index 0 |
| “Incomplete type” error | Can’t use ::type or ::value |
Accessing before instantiation | Ensure all needed types are fully defined |
| “No matching overload” | SFINAE removed all options | Constraints too strict | Check your enable_if / concept conditions |
| “Ambiguous specialization” | Two specializations match | Overlapping patterns | Make specializations more specific |
| constexpr function not constexpr | Can’t use in static_assert | Non-constexpr operation inside | Remove heap allocation, virtual calls, etc. |
| Template parameter deduction failed | Can’t deduce types | Deduction context issue | Provide explicit template args or deduction guide |
Debugging Tips:
- Print types at compile time:
template<typename T> struct Debug; // Usage: Debug<SomeType>{}; // Error shows what SomeType is - Check intermediate types:
using Step1 = Filter<L, Pred>; using Step2 = Transform<Step1, F>; static_assert(!std::is_same_v<Step1, void>); // Verify step 1 worked - Use concepts for clearer errors:
```cpp
template
concept HasValueMember = requires { T::value; };
template
---
## 8. Extensions & Challenges
### Extension 1: Compile-Time Regex
```cpp
constexpr auto pattern = regex<R"(\d+)">();
static_assert(pattern.match("123"));
static_assert(!pattern.match("abc"));
Extension 2: Type-Level State Machine
using Machine = StateMachine<
State<"idle", Transition<"start", "running">>,
State<"running", Transition<"stop", "idle">>
>;
static_assert(Machine::transition("idle", "start") == "running");
Extension 3: Compile-Time SQL Parser
constexpr auto query = sql<"SELECT name, age FROM users WHERE id = ?">();
static_assert(query.columns[0] == "name");
static_assert(query.table == "users");
Extension 4: Boost.Hana-style Value Semantics
constexpr auto list = make_tuple(1_c, 2_c, 3_c); // Integral constants
constexpr auto doubled = transform(list, [](auto x) { return x * 2_c; });
static_assert(doubled[0_c] == 2_c);
9. Real-World Connections
Eigen (Linear Algebra Library):
- Uses expression templates to fuse operations
(A * B + C)creates a type representing the expression- Evaluation is deferred until assignment
Boost.Spirit (Parser Library):
- Parsers are types, combined with operators
int_ >> ',' >> double_builds a parser type- Compile-time grammar checking
Boost.Hana (Metaprogramming):
- Types have value semantics
hana::tuple<int, double>behaves like a runtime tuple- Unifies TMP with runtime programming
nlohmann/json (JSON Library):
- Uses SFINAE/concepts for type detection
json j = myStruct;works via TMP
10. Resources
Documentation
Tutorials
- Fluent C++: Template Metaprogramming
- Modern C++ Metaprogramming (video)
- CppCon 2014: Walter E. Brown “Modern Template Metaprogramming”
Libraries to Study
- Boost.MP11 - Modern type list library
- Boost.Hana - Metaprogramming with value semantics
- kvasir::mpl - Fast metaprogramming
11. Self-Assessment Checklist
TypeList Operations
Length<List>returns correct countAt<List, I>accesses correct typeContains<List, T>correctly detects presenceIndexOf<List, T>returns correct index (or sentinel)PushFront/PushBackadd types correctlyPopFront/PopBackremove types correctlyConcat<L1, L2>concatenates listsFilter<List, Pred>keeps matching typesTransform<List, F>applies metafunctionUnique<List>removes duplicatesReverse<List>reverses order
FixedString
- Construction from string literal works
size()returns correct lengthoperator[]accesses charactersoperator+concatenates stringsoperator==compares strings- Can be used as template parameter
Compile-Time JSON
- Parses null, bool, number, string
- Parses arrays
- Parses objects
- Nested structures work
operator[]accesses elements
General
- All tests are static_assert (compile-time)
- Error messages are helpful
- Code compiles with -Wall -Wextra -Werror
12. Submission / Completion Criteria
Your implementation is complete when:
- All TypeList operations work:
using L = TypeList<int, double, char, bool>; static_assert(Length<L>::value == 4); static_assert(std::is_same_v<At<L, 1>, double>); static_assert(Contains<L, char>::value); static_assert(IndexOf<L, bool>::value == 3); static_assert(std::is_same_v< Filter<L, std::is_integral>, TypeList<int, char, bool> >); - FixedString manipulation works:
constexpr auto s = FixedString{"Hello"} + ", " + "World!"; static_assert(s == "Hello, World!"); - Compile-time JSON (at least basic) works:
constexpr auto j = R"({"x": 10})"_json; static_assert(j["x"] == 10); - Zero runtime cost:
// Assembly output shows no runtime computation // All values are compile-time constants - Clean compilation:
g++ -std=c++20 -Wall -Wextra -Werror tests.cpp ./a.out # Prints "All tests passed" if it runs
Template metaprogramming is C++’s dark magic—computation without runtime. Master this, and you’ll understand how the STL, Boost, and high-performance libraries achieve zero-cost abstractions. Every std::tuple, std::variant, and std::optional uses these techniques.