Skip to content

CRTP

The Curiously Recurring Template Pattern (CRTP) is a powerful C++ template technique that enables static polymorphism and compile-time interface enforcement. It's particularly valuable in low-latecy systems where runtime polymorphism overhead is unacceptable.

What is CRTP?

CRTP is a pattern where a base class template takes the derived class as a template parameter, allowing the base class to access the derived class's interface at compile time.

cpp
template<typename Derived>
class Base {
public:
    void interface() {
        static_cast<Derived*>(this)->implementation();
    }

    void common_operation() {
        // Common functionality
        static_cast<Derived*>(this)->implementation();
    }
};

class Derived : public Base<Derived> {
public:
    void implementation() {
        // Derived-specific implementation
    }
};

Key Benefits of CRTP

1. Static Polymorphism

Unlike virtual functions, CRTP provides polymorphism without runtime overhead:

cpp
template<typename Derived>
class Shape {
public:
    double area() const {
        return static_cast<const Derived*>(this)->area_impl();
    }

    double perimeter() const {
        return static_cast<const Derived*>(this)->perimeter_impl();
    }
};

class Circle : public Shape<Circle> {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}

    double area_impl() const {
        return 3.14159 * radius * radius;
    }

    double perimeter_impl() const {
        return 2 * 3.14159 * radius;
    }
};

class Rectangle : public Shape<Rectangle> {
private:
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}

    double area_impl() const {
        return width * height;
    }

    double perimeter_impl() const {
        return 2 * (width + height);
    }
};

2. Compile-Time Interface Enforcement

CRTP ensures that derived classes implement required methods:

cpp
template<typename Derived>
class MarketDataHandler {
public:
    void process_data(const MarketData& data) {
        // Common processing logic
        static_cast<Derived*>(this)->validate_data(data);
        static_cast<Derived*>(this)->transform_data(data);
        static_cast<Derived*>(this)->store_data(data);
    }

private:
    // Force derived classes to implement these
    void validate_data(const MarketData& data) = delete;
    void transform_data(const MarketData& data) = delete;
    void store_data(const MarketData& data) = delete;
};

class EquityHandler : public MarketDataHandler<EquityHandler> {
public:
    void validate_data(const MarketData& data) {
        // Equity-specific validation
    }

    void transform_data(const MarketData& data) {
        // Equity-specific transformation
    }

    void store_data(const MarketData& data) {
        // Equity-specific storage
    }
};

Example Applications of CRTP

cpp
template<typename Strategy>
class TradingEngine {
private:
    Strategy strategy;

public:
    void execute_order(const Order& order) {
        strategy.execute(order);
    }

    void cancel_order(OrderId id) {
        strategy.cancel(id);
    }
};

class AggressiveStrategy {
public:
    void execute(const Order& order) {
        // Aggressive execution logic
    }

    void cancel(OrderId id) {
        // Aggressive cancellation logic
    }
};

class ConservativeStrategy {
public:
    void execute(const Order& order) {
        // Conservative execution logic
    }

    void cancel(OrderId id) {
        // Conservative cancellation logic
    }
};

// Usage
TradingEngine<AggressiveStrategy> aggressive_engine;
TradingEngine<ConservativeStrategy> conservative_engine;

Limitations and Considerations

1. No Runtime Polymorphism

CRTP provides static polymorphism, so you cannot change behavior at runtime:

cpp
// This doesn't work with CRTP
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(3.0, 4.0));

// Must use templates
std::vector<Circle> circles;
std::vector<Rectangle> rectangles;

2. Template Code Bloat

Each derived class creates a new instantiation of the base class template:

cpp
// Creates separate code for each derived class
Circle circle(5.0);      // Shape<Circle> instantiation
Rectangle rect(3.0, 4.0); // Shape<Rectangle> instantiation

3. Compilation Time

CRTP can increase compilation time due to template instantiation overhead.

Summary

CRTP is a powerful technique that provides:

  1. Zero-cost abstraction for performance-critical code
  2. Compile-time interface enforcement without runtime overhead
  3. Static polymorphism for HFT systems
  4. Type safety at compile time
  5. Flexible design patterns for complex systems

Understanding CRTP is essential for writing high-performance C++ code, especially in domains like HFT where every nanosecond matters.

Your Task

Implement a CRTP-based trading strategy framework.

  • Create a base class template 'TradingStrategy' that takes the derived strategy as a template parameter.
  • The base class should provide common functionality like position sizing and risk management.
  • Each derived strategy must implement three methods: calculate_position() (returns position size), should_trade() (returns bool), and get_risk_level() (returns risk level enum). Implement three derived strategies: MomentumStrategy, MeanReversionStrategy, and ArbitrageStrategy, each with their own trading logic.

Create the base class template 'TradingStrategy' and provides common functionality like position sizing and risk management.

cpp
Medium,
    High
};

// Market data structure
struct MarketData {
    double price;
    double volume;
    double momentum;
    double mean_price;
    double spread;

    MarketData(double p, double v, double m, double mp, double s) 
        : price(p), volume(v), momentum(m), mean_price(mp), spread(s) {}
};

// TODO: Implement CRTP-based trading strategy framework
//
// Requirements:
// 1. Create a base class template 'TradingStrategy' that takes the derived strategy as a template 
parameter
// 2. The base class should provide common functionality like position sizing and risk management
// 3. Each derived strategy must implement three methods:
//    - calculate_position() (returns position size as int)
//    - should_trade() (returns bool)
//    - get_risk_level() (returns RiskLevel enum)
// 4. Implement three derived strategies:
//    - MomentumStrategy: trades based on price momentum
//    - MeanReversionStrategy: trades based on mean reversion
//    - ArbitrageStrategy: trades based on price spreads
//
// This demonstrates:
// - CRTP pattern for static polymorphism
// - Compile-time interface enforcement
// - Zero-cost abstraction for trading systems
// - Template class design for strategy patterns

// Base class template using CRTP
template<typename Derived>
class TradingStrategy {
public:
    // Common functionality provided by base class
    void execute_strategy(const MarketData& data) {
        // TODO: calculate position and risk level from derived class
        // TODO: apply position sizing and risk management using obtained values
    }

    // Common methods that derived classes can use
    void apply_position_sizing(int position) {
        std::cout << "Position: " << position << std::endl;
    }

    void apply_risk_management(RiskLevel risk) {
        std::cout << "Risk level: ";
        switch (risk) {
            case RiskLevel::Low: std::cout << "Low"; break;
            case RiskLevel::Medium: std::cout << "Medium"; break;
            case RiskLevel::High: std::cout << "High"; break;
        }
        std::cout << std::endl;
    }

private:
    // TODO: Force derived classes to implement these methods
};

// TODO: Implement MomentumStrategy
// This strategy should:
// - calculate_position(): Return positive position if momentum > 0, negative if < 0
// - should_trade(): Return true if momentum is significant (abs > 0.1)
// - get_risk_level(): Return Medium risk level
class MomentumStrategy : public TradingStrategy<MomentumStrategy> {
private:
    MarketData current_data;

public:
    MomentumStrategy(const MarketData& data) : current_data(data) {}

    // TODO: Implement these three methods
    int calculate_position() {
        // Your implementation here
    }

    bool should_trade() {
        // Your implementation here
    }

    RiskLevel get_risk_level() {
        // Your implementation here
    }
};

// TODO: Implement MeanReversionStrategy
// This strategy should:
// - calculate_position(): Return negative position if price > mean_price, positive if < mean_price
// - should_trade(): Return true if price deviation from mean is significant (abs > 0.05)
// - get_risk_level(): Return Low risk level
class MeanReversionStrategy : public TradingStrategy<MeanReversionStrategy> {
private:
    MarketData current_data;

public:
    MeanReversionStrategy(const MarketData& data) : current_data(data) {}

    // TODO: Implement these three methods
    int calculate_position() {
        // Your implementation here
    }

    bool should_trade() {
        // Your implementation here
    }

    RiskLevel get_risk_level() {
        // Your implementation here
    }
};

// TODO: Implement ArbitrageStrategy
// This strategy should:
// - calculate_position(): Return large position (200) if spread is significant
// - should_trade(): Return true if spread is significant (abs > 0.02)
// - get_risk_level(): Return High risk level
class ArbitrageStrategy : public TradingStrategy<ArbitrageStrategy> {
private:
    MarketData current_data;

public:
    ArbitrageStrategy(const MarketData& data) : current_data(data) {}

    // TODO: Implement these three methods
    int calculate_position() {
        // Your implementation here
    }

    bool should_trade() {
// Risk level enum
enum class RiskLevel {
    Low,
#include <string>

#include <iostream>
#include <vector>