Skip to Content
Course content

59: Template Metaprogramming Basics

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you first run into template metaprogramming (TMP), it usually feels like you've stumbled into a different language entirely. It looks like C++, but it behaves like a functional language where everything is immutable and recursion is your only tool. Let's clear up the confusion.

Wait, isn't this just generic programming?

Not quite. When you write a std::vector<T>, you're doing generic programming. You're telling the compiler, "I don't care what T is; just make the logic work for any type." That's about code reuse.

Template Metaprogramming is different. It's about computation. With TMP, you're using the compiler as an execution engine to calculate values or generate types before the program even starts running. If a calculation happens during TMP, the result is a constant by the time the CPU ever sees your binary. I've used this in the past to pre-calculate lookup tables for physics engines, which saves precious milliseconds during the game loop because the math is already "done."

How do I actually "calculate" things without a loop?

This is where most people get tripped up. You can't use for or while loops inside a template definition because those are runtime constructs. To "loop" in TMP, we use template recursion and template specialization.

Think of specialization as your "if" statement or your "base case." Here is the classic example: calculating a factorial at compile time.

template<int N>
struct Factorial {
    static constexpr int value = N * Factorial<N - 1>::value;
};

// This is the specialization. It stops the recursion.
template<>
struct Factorial<0> {
    static constexpr int value = 1;
};

// Usage:
int main() {
    // The compiler calculates this. The binary literally just contains the number 120.
    int result = Factorial<5>::value; 
}

I'll be honest: writing structs for everything is clunky. You're essentially creating a new type for every single step of the calculation. It's a bit of a memory hog for the compiler, but it's incredibly powerful for ensuring correctness before the code even runs.

Do I even need this if I have constexpr?

You're asking the right question. In modern C++ (C++11 and later, especially C++14/17), constexpr functions have replaced a huge chunk of old-school TMP. A constexpr function looks like a normal function, but the compiler can evaluate it at compile time if the inputs are known.

So why learn the "hard way" with templates? Because constexpr only handles values. TMP handles types. If you need to conditionally remove a const qualifier from a type, or if you want to create a tuple of types based on some logic, constexpr can't help you. You need the template engine for that. You'll see this heavily in the <type_traits> library, which is the backbone of almost every professional C++ library today.




📋 Practical Task

Exercise: The Compile-Time Power Calculator

Your goal is to implement a template metaprogram that calculates the power of an integer (base^exp) at compile time. You cannot use the std::pow function, as that is a runtime function.

  • Create a template struct called Power that takes two template parameters: int Base and int Exp.
  • Implement the recursive step: Base^Exp = Base * Power<Base, Exp - 1>.
  • Implement a template specialization for the base case where Exp == 0 (which should return 1).
  • In your main function, use a static_assert to verify that Power<2, 10>::value is exactly 1024.

Remember: if the static_assert passes, the code will compile. If you got the math wrong, the compiler will throw an error and refuse to build the program—which is exactly the point of TMP!

Rating
0 0

There are no comments for now.

to be the first to leave a comment.