This is the second article in series C++ Metaprogramming series, you can find the first article here C++ Metaprogramming: Variadic Templates & Fold Expressions. This article will unveil the practical usage of templates and constexpr for compile-time code execution.

Introduction

Compile-time calculations in C++ allow some operations to be performed during compilation instead of at runtime. This provides a number of advantages: validating data and logic before running the program, reducing overall execution time, and enhancing code safety and reliability. At the same time, of course, there is an additional cost: compilation time could increase significantly, and the code itself may become more difficult to read.

In this article we will explore:

Templates Metaprogramming - the foundation of compile-time calculations

Historically in C++ metaprogramming emerged thanks to templates. They originally were intended for generating generic functions and classes, but over time it was discovered, that recursive templates could be used to preform quite complex computations.

Classical examples

Example 1: Factorial using template recursion

#include <cstdio>

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

template <>
struct Factorial<0> {
    static constexpr int value = 1;
};

int main() {
  printf("%d\n", Factorial<5>::value);
  return 0;
}

Here, the computation of Factorial<5>::value takes place at compile time. However, this approach is difficult to read, requires specializations and overloads the compiler.

Example 2: Fibonacci Numbers

#include <cstdio>

template <int N>
struct Fibonacci {
    static constexpr int value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};

template <>
struct Fibonacci<0> {
    static constexpr int value = 0;
};

template <>
struct Fibonacci<1> {
    static constexpr int value = 1;
};

int main() {
  printf("%d\n", Fibonacci<10>::value);
  return 0;
}

Recursive templates are good for demonstrating ideas, but in real enterprise code they quickly become cumbersome.

Drawbacks and limitations

Nevertheless, templates metaprogramming is still successfully used today, including in the modern libraries, though more often for working with types than for purely mathematical operations.

Modern approach: constexpr

With the introduction of the constexpr specifier in C++11, template hacks began to take a back seat. The keyword constexpr informs the compiler that a function or an object can (and should, if possible) be evaluated at compile time.

Basics of constexpr

Example 3: Factorial using constexpr

#include <cstdio>

constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

int main() {
  printf("%d\n", factorial(5));
  return 0;
}

This core is simpler than the template-based version and easier to read. Moreover, if factorial(5) is called with a constant, the result will be computed at compile time.

Advanced aspects of constexpr

#include <cstdio>

constexpr int sum_to_n(int n) {
    int sum = 0;
    for (int i = 1; i <= n; ++i) {
        sum += i;
    }
    return sum;
}

int main() {
  printf("%d\n", sum_to_n(15));
  return 0;
}

Example 5: constexpr and classes

#include <cstdio>
#include <cmath>

struct Point {
    int x, y;

    constexpr Point(int x, int y) : x(x), y(y) {}
    constexpr float len() const { return sqrt(x * x + y * y); }
};

constexpr Point p(3, 4);

int main() {
  printf("%f\n", p.len());
  return 0;
}

Useful features for compile-time computations

Combining Templates and constexpr

Although constexpr often simplifies the task, there are situations in which templates remain necessary:

Often it is more efficient and clear to perform all calculations using constexpr, and to use templates only where metaprogramming on types is truly needed. This approach strikes a balance between flexibility and ease of debugging.

Pitfalls

Conclusion

Modern C++ offers developers a wide range of tools for organizing compile-time computations - from classic template-based metaprogramming to the more intuitive and flexible constexpr. When used wisely, these tools can significantly improve code performance and safety by catching entire classes of errors early.

At the same time, it's important to weigh the trade-offs: will your code turn into an unreadable monolith, or will compilation times become unreasonably long? Within reasonable limits, metaprogramming and constexpr can make your project more efficient and reliable, offering tangible benefits when developing large-scale systems.

Try It Yourself on GitHub

If you want to explore these examples hands-on, feel free to visit my GitHub repository where you’ll find all the source files for the code in this article. You can clone the repository, open the code in your favorite IDE or build system, and experiment with constexpr to see how the compiler works with it. Enjoy playing around with the examples!

Follow me

LinkedIn

Github