NV
NordVarg
ServicesTechnologiesIndustriesCase StudiesBlogAboutContact
Get Started

Footer

NV
NordVarg

Software Development & Consulting

GitHubLinkedInTwitter

Services

  • Product Development
  • Quantitative Finance
  • Financial Systems
  • ML & AI

Technologies

  • C++
  • Python
  • Rust
  • OCaml
  • TypeScript
  • React

Company

  • About
  • Case Studies
  • Blog
  • Contact

© 2025 NordVarg. All rights reserved.

November 24, 2025
•
NordVarg Team
•

C++ Template Metaprogramming for Financial DSLs

Systems ProgrammingC++template-metaprogrammingDSLexpression-templatesCRTPconstexprzero-overhead
9 min read
Share:

TL;DR – C++ templates enable compile-time DSLs with zero runtime cost. This guide shows expression templates, type-level computations, and compile-time validation for financial calculations.

1. Why Template Metaprogramming for Finance?#

Templates provide:

  • Zero-overhead abstractions – Compiled away at build time
  • Type safety – Compile-time validation of formulas
  • Domain-specific syntax – Natural mathematical notation
  • Performance – Inlining and optimization opportunities
  • Compile-time computation – Constants computed at build time

2. Expression Templates for Lazy Evaluation#

Build expression trees at compile time:

cpp
1#include <iostream>
2#include <cmath>
3
4// Base expression
5template<typename E>
6struct Expr {
7    double operator[](size_t i) const {
8        return static_cast<const E&>(*this)[i];
9    }
10    
11    size_t size() const {
12        return static_cast<const E&>(*this).size();
13    }
14};
15
16// Vector wrapper
17class Vector : public Expr<Vector> {
18    double* data_;
19    size_t size_;
20    
21public:
22    Vector(size_t n) : data_(new double[n]), size_(n) {}
23    ~Vector() { delete[] data_; }
24    
25    double operator[](size_t i) const { return data_[i]; }
26    double& operator[](size_t i) { return data_[i]; }
27    size_t size() const { return size_; }
28    
29    // Assignment from expression
30    template<typename E>
31    Vector& operator=(const Expr<E>& expr) {
32        for (size_t i = 0; i < size_; ++i) {
33            data_[i] = expr[i];
34        }
35        return *this;
36    }
37};
38
39// Binary operation
40template<typename Op, typename L, typename R>
41struct BinOp : public Expr<BinOp<Op, L, R>> {
42    const L& lhs;
43    const R& rhs;
44    
45    BinOp(const L& l, const R& r) : lhs(l), rhs(r) {}
46    
47    double operator[](size_t i) const {
48        return Op::apply(lhs[i], rhs[i]);
49    }
50    
51    size_t size() const { return lhs.size(); }
52};
53
54// Operations
55struct Add {
56    static double apply(double a, double b) { return a + b; }
57};
58
59struct Mul {
60    static double apply(double a, double b) { return a * b; }
61};
62
63// Operator overloads
64template<typename L, typename R>
65BinOp<Add, L, R> operator+(const Expr<L>& lhs, const Expr<R>& rhs) {
66    return BinOp<Add, L, R>(
67        static_cast<const L&>(lhs),
68        static_cast<const R&>(rhs)
69    );
70}
71
72template<typename L, typename R>
73BinOp<Mul, L, R> operator*(const Expr<L>& lhs, const Expr<R>& rhs) {
74    return BinOp<Mul, L, R>(
75        static_cast<const L&>(lhs),
76        static_cast<const R&>(rhs)
77    );
78}
79
80// Usage: portfolio value calculation
81Vector prices(1000);
82Vector quantities(1000);
83Vector weights(1000);
84
85// Lazy evaluation - no temporaries
86Vector portfolio_value(1000);
87portfolio_value = prices * quantities * weights;
88

Benefit: Zero temporary allocations, optimal code generation.

3. Compile-Time Unit Checking#

Type-safe dimensional analysis:

cpp
1#include <ratio>
2
3template<int M, int L, int T>
4struct Unit {
5    double value;
6    
7    constexpr explicit Unit(double v) : value(v) {}
8    
9    constexpr double get() const { return value; }
10};
11
12// Dimension aliases
13template<typename T> using Scalar = Unit<0, 0, 0>;
14template<typename T> using Price = Unit<1, 0, 0>;  // Money
15template<typename T> using Quantity = Unit<0, 1, 0>;  // Count
16template<typename T> using Time = Unit<0, 0, 1>;  // Time
17
18// Multiplication: dimensions add
19template<int M1, int L1, int T1, int M2, int L2, int T2>
20constexpr Unit<M1+M2, L1+L2, T1+T2> operator*(
21    Unit<M1, L1, T1> lhs,
22    Unit<M2, L2, T2> rhs
23) {
24    return Unit<M1+M2, L1+L2, T1+T2>(lhs.value * rhs.value);
25}
26
27// Division: dimensions subtract
28template<int M1, int L1, int T1, int M2, int L2, int T2>
29constexpr Unit<M1-M2, L1-L2, T1-T2> operator/(
30    Unit<M1, L1, T1> lhs,
31    Unit<M2, L2, T2> rhs
32) {
33    return Unit<M1-M2, L1-L2, T1-T2>(lhs.value / rhs.value);
34}
35
36// Addition: same dimensions only
37template<int M, int L, int T>
38constexpr Unit<M, L, T> operator+(Unit<M, L, T> lhs, Unit<M, L, T> rhs) {
39    return Unit<M, L, T>(lhs.value + rhs.value);
40}
41
42// Usage: compile-time type checking
43constexpr Price<double> price{100.50};
44constexpr Quantity<double> qty{1000};
45constexpr auto notional = price * qty;  // Unit<1, 1, 0> - valid
46
47// This won't compile: type mismatch
48// auto invalid = price + qty;  // Error: different dimensions
49

Safety: Dimensional errors caught at compile time.

4. CRTP for Static Polymorphism#

Curiously Recurring Template Pattern:

cpp
1template<typename Derived>
2class Instrument {
3public:
4    double npv() const {
5        return static_cast<const Derived*>(this)->npv_impl();
6    }
7    
8    double delta() const {
9        return static_cast<const Derived*>(this)->delta_impl();
10    }
11};
12
13class EuropeanOption : public Instrument<EuropeanOption> {
14    double spot_, strike_, rate_, vol_, time_;
15    
16public:
17    EuropeanOption(double s, double k, double r, double v, double t)
18        : spot_(s), strike_(k), rate_(r), vol_(v), time_(t) {}
19    
20    double npv_impl() const {
21        // Black-Scholes formula
22        double d1 = (std::log(spot_ / strike_) + 
23                     (rate_ + 0.5 * vol_ * vol_) * time_) / 
24                    (vol_ * std::sqrt(time_));
25        double d2 = d1 - vol_ * std::sqrt(time_);
26        
27        return spot_ * norm_cdf(d1) - 
28               strike_ * std::exp(-rate_ * time_) * norm_cdf(d2);
29    }
30    
31    double delta_impl() const {
32        double d1 = (std::log(spot_ / strike_) + 
33                     (rate_ + 0.5 * vol_ * vol_) * time_) / 
34                    (vol_ * std::sqrt(time_));
35        return norm_cdf(d1);
36    }
37    
38private:
39    static double norm_cdf(double x) {
40        return 0.5 * (1.0 + std::erf(x / std::sqrt(2.0)));
41    }
42};
43
44// Generic pricing function
45template<typename T>
46double price_instrument(const Instrument<T>& inst) {
47    return inst.npv();  // Statically dispatched, no vtable
48}
49
50// Usage
51EuropeanOption option(100, 100, 0.05, 0.2, 1.0);
52double price = price_instrument(option);  // Zero overhead
53

Performance: No virtual function overhead, fully inlined.

5. Variadic Templates for Generic Algorithms#

Type-safe variadic functions:

cpp
1#include <tuple>
2#include <type_traits>
3
4// Fold expression for sum
5template<typename... Args>
6constexpr auto sum(Args... args) {
7    return (args + ...);
8}
9
10// Generic Monte Carlo simulation
11template<typename RNG, typename Payoff, typename... Params>
12double monte_carlo(size_t n_sims, RNG& rng, Payoff payoff, Params... params) {
13    double sum = 0.0;
14    
15    for (size_t i = 0; i < n_sims; ++i) {
16        sum += payoff(rng, params...);
17    }
18    
19    return sum / n_sims;
20}
21
22// Example payoff function
23struct CallPayoff {
24    template<typename RNG>
25    double operator()(RNG& rng, double spot, double strike, double vol, double time) const {
26        double z = rng.normal();
27        double st = spot * std::exp(-0.5 * vol * vol * time + vol * std::sqrt(time) * z);
28        return std::max(0.0, st - strike);
29    }
30};
31
32// Usage
33std::mt19937 rng;
34CallPayoff payoff;
35double price = monte_carlo(1'000'000, rng, payoff, 100.0, 100.0, 0.2, 1.0);
36

Flexibility: Type-safe, arbitrary parameters.

6. Constexpr for Compile-Time Pricing#

Compute prices at compile time:

cpp
1constexpr double constexpr_sqrt(double x, double guess = 1.0, int iter = 0) {
2    return iter == 10 ? guess :
3           constexpr_sqrt(x, (guess + x / guess) / 2.0, iter + 1);
4}
5
6constexpr double constexpr_exp(double x) {
7    double result = 1.0;
8    double term = 1.0;
9    for (int i = 1; i < 20; ++i) {
10        term *= x / i;
11        result += term;
12    }
13    return result;
14}
15
16constexpr double constexpr_log(double x) {
17    // Newton's method
18    double y = x - 1.0;
19    double result = 0.0;
20    double term = y;
21    for (int i = 1; i < 20; ++i) {
22        result += term / i;
23        term *= -y;
24    }
25    return result;
26}
27
28// Compile-time Black-Scholes (simplified)
29constexpr double black_scholes_constexpr(
30    double spot, double strike, double rate, double vol, double time
31) {
32    double d1 = (constexpr_log(spot / strike) + 
33                 (rate + 0.5 * vol * vol) * time) / 
34                (vol * constexpr_sqrt(time));
35    
36    // Simplified - full implementation needs erf
37    return spot * 0.5 - strike * constexpr_exp(-rate * time) * 0.5;
38}
39
40// Compile-time constant
41constexpr double atm_call_price = black_scholes_constexpr(100, 100, 0.05, 0.2, 1.0);
42static_assert(atm_call_price > 0.0, "Price must be positive");
43

Benefit: Validation at compile time, zero runtime cost.

7. Type Traits for Concept Checking#

Pre-C++20 concept emulation:

cpp
1#include <type_traits>
2
3// Check if type has npv() method
4template<typename T, typename = void>
5struct has_npv : std::false_type {};
6
7template<typename T>
8struct has_npv<T, std::void_t<decltype(std::declval<T>().npv())>> 
9    : std::true_type {};
10
11// Check if type is priceable
12template<typename T>
13constexpr bool is_priceable_v = has_npv<T>::value;
14
15// Generic pricing with SFINAE
16template<typename T>
17std::enable_if_t<is_priceable_v<T>, double>
18price(const T& instrument) {
19    return instrument.npv();
20}
21
22// Won't compile for non-priceable types
23// double p = price(42);  // Error: int is not priceable
24

Safety: Type constraints enforced at compile time.

8. Template Specialization for Optimization#

Optimize for specific types:

cpp
1// Generic matrix multiplication
2template<typename T>
3void matmul(const T* A, const T* B, T* C, size_t n) {
4    for (size_t i = 0; i < n; ++i) {
5        for (size_t j = 0; j < n; ++j) {
6            C[i * n + j] = 0;
7            for (size_t k = 0; k < n; ++k) {
8                C[i * n + j] += A[i * n + k] * B[k * n + j];
9            }
10        }
11    }
12}
13
14// Specialized for double - use BLAS
15template<>
16void matmul<double>(const double* A, const double* B, double* C, size_t n) {
17    // Call optimized BLAS routine
18    cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
19                n, n, n, 1.0, A, n, B, n, 0.0, C, n);
20}
21

Performance: Optimal code path per type.

9. DSL for Trade Specifications#

Embedded domain-specific language:

cpp
1// Trade DSL
2struct Trade {
3    std::string symbol;
4    double quantity;
5    double price;
6    
7    Trade& buy(double qty) {
8        quantity = qty;
9        return *this;
10    }
11    
12    Trade& at(double p) {
13        price = p;
14        return *this;
15    }
16};
17
18Trade operator""_shares(const char* sym, size_t) {
19    return Trade{sym, 0, 0};
20}
21
22// Usage: natural syntax
23auto trade = "AAPL"_shares.buy(100).at(150.50);
24

Readability: Domain-specific syntax for clarity.

10. Production Example: Covariance Matrix#

Compile-time matrix operations:

cpp
1template<size_t N>
2class CovarianceMatrix {
3    std::array<std::array<double, N>, N> data_;
4    
5public:
6    constexpr CovarianceMatrix() : data_{} {}
7    
8    constexpr double& operator()(size_t i, size_t j) {
9        return data_[i][j];
10    }
11    
12    constexpr double operator()(size_t i, size_t j) const {
13        return data_[i][j];
14    }
15    
16    // Compile-time Cholesky decomposition
17    constexpr CovarianceMatrix<N> cholesky() const {
18        CovarianceMatrix<N> L;
19        
20        for (size_t i = 0; i < N; ++i) {
21            for (size_t j = 0; j <= i; ++j) {
22                double sum = 0.0;
23                
24                for (size_t k = 0; k < j; ++k) {
25                    sum += L(i, k) * L(j, k);
26                }
27                
28                if (i == j) {
29                    L(i, j) = constexpr_sqrt((*this)(i, i) - sum);
30                } else {
31                    L(i, j) = ((*this)(i, j) - sum) / L(j, j);
32                }
33            }
34        }
35        
36        return L;
37    }
38};
39
40// Compile-time computation
41constexpr CovarianceMatrix<3> cov = []() {
42    CovarianceMatrix<3> m;
43    m(0, 0) = 1.0; m(0, 1) = 0.5; m(0, 2) = 0.3;
44    m(1, 0) = 0.5; m(1, 1) = 1.0; m(1, 2) = 0.4;
45    m(2, 0) = 0.3; m(2, 1) = 0.4; m(2, 2) = 1.0;
46    return m;
47}();
48
49constexpr auto L = cov.cholesky();  // Computed at compile time
50

Benefit: Matrix decomposition done at build time.


C++ template metaprogramming enables zero-overhead domain-specific languages for financial calculations. Use expression templates for lazy evaluation, constexpr for compile-time computation, and CRTP for static polymorphism to build high-performance, type-safe pricing libraries.

NT

NordVarg Team

Technical Writer

NordVarg Team is a software engineer at NordVarg specializing in high-performance financial systems and type-safe programming.

C++template-metaprogrammingDSLexpression-templatesCRTP

Join 1,000+ Engineers

Get weekly insights on building high-performance financial systems, latest industry trends, and expert tips delivered straight to your inbox.

✓Weekly articles
✓Industry insights
✓No spam, ever

Related Posts

Nov 24, 2025•8 min read
Modern C++ for Ultra-Low Latency: C++20/23 in Production
Systems ProgrammingC++C++20
Nov 24, 2025•9 min read
Rust Unsafe: When and How to Use It Safely in Financial Systems
Systems ProgrammingRustunsafe
Nov 24, 2025•7 min read
Rust for Financial Systems: Beyond Memory Safety
Systems ProgrammingRustlow-latency

Interested in working together?