Compile-Time Limit Order Book in C++ — Price Levels and Compile-Time Queues (Part 3)
This article is Part 3 in a five-part series where we build a compile-time limit order book using modern C++ template metaprogramming.
Compile-Time Limit Order Book in C++ — Price Levels and Compile-Time Queues (Part 3)
This article is Part 3 in a five-part series where we build a compile-time limit order book using modern C++ template metaprogramming.
If you haven’t read Parts 1 and 2, I strongly recommend doing so before continuing. They introduce the motivation, design constraints, and the Order template that we will build upon here.
Part 1 — Compile-Time Limit Order Book in C++ — A Template Metaprogramming Deep Dive Part 2 — Order Representation at Compile Time Part 3 — Price Levels and Compile-Time Queues (This article) Part 4 — Merge Sort and List of Price Levels Part 5 — Bringing It All Together The full source code is published on github — RishabhGarg108/compile_time_orderbook
What We Will Build in This Article
In this article, we introduce the concept of a price level and implement it entirely at compile time. By the end of this article, we will have:
- A generic compile-time FIFO queue
- A price level abstraction consisting of:
- A price
- A time-priority-preserving queue of orders
- Compile-time operations to:
- Add an order to a level
- Remove an order from a level by
orderId
Along the way, we will cover two important template metaprogramming concepts:
- Expressing
if / elselogic usingstd::conditional_t - Understanding the difference between a template and a type, and how the global scope resolution operator (
::) helps disambiguate them
🧠 Mental Model: How Recursive TMP Actually Executes
Every recursive template instantiation is a compile-time function call.
- Template parameters are the inputs
- The
typealias is the return value - Partial specializations act like
if / elsebranches
The compiler repeatedly:
- Matches the most specialized template
- Instantiates it
- Substitutes parameters
- Repeats until a base case is reached
If two base cases are equally valid, compilation fails — even if the algorithm itself is correct.
A Generic Compile-Time Queue
At each price level, orders must be stored according to time priority: orders that arrive earlier must be matched before later ones.
Conceptually, this is just a FIFO queue.
Why a Generic Queue?
At first glance, it might seem odd to build a queue that can store arbitrary types instead of only Orders.
In runtime programming, all orders would be instances of the same type. In template metaprogramming, however, each order is a distinct type:
Order<1, BUY, 100, 5>
Order<2, SELL, 101, 5>
These are not objects — they are different types. Therefore, our queue must be able to hold heterogeneous types.
Queue Interface
- Queue: It is represented as a variadic type list. Elements at the front appear earlier in the parameter pack.
- QueuePush: It appends a new element to the end of the queue, preserving FIFO order.
- QueuePop: It removes the front element of the queue.
- QueueMerge: It will be useful when implementing order removal.
/*////////////////////////////////////////////////
Queue: Generic queue that can hold heterogeneous
data types. It provides ability to push at the end
of queue and pop from the front.
////////////////////////////////////////////////*/
template <typename... T>
struct Queue
{
static constexpr int size = sizeof...(T);
};
/*////////////////////////////////////////////////
QueuePush: Pushes an element at the end of the
queue.
////////////////////////////////////////////////*/
template <typename Q, typename T>
struct QueuePush;
template <typename T, typename... QElem>
struct QueuePush<Queue<QElem...>, T>
{
using type = Queue<QElem..., T>;
};
template <typename Q, typename T>
using QueuePush_t = typename QueuePush<Q, T>::type;
/*////////////////////////////////////////////////
QueuePop: Pops an element front the front of the
queue.
////////////////////////////////////////////////*/
template <typename Q>
struct QueuePop;
template <typename T0, typename... T1toN>
struct QueuePop<Queue<T0, T1toN...>>
{
using type = Queue<T1toN...>;
};
template <typename Q>
using QueuePop_t = typename QueuePop<Q>::type;
/*////////////////////////////////////////////////
QueueMerge: Merges two queues by pushing elements
from the second queue to the end of first queue.
////////////////////////////////////////////////*/
template<typename Q1, typename Q2>
struct QueueMerge;
template<typename... Q1, typename... Q2>
struct QueueMerge<Queue<Q1...>, Queue<Q2...>>
{
using type = Queue<Q1...,Q2...>;
};
template<typename Q1, typename Q2>
using QueueMerge_t = typename QueueMerge<Q1, Q2>::type;
Note: If partial specialization and variadic templates are unfamiliar, it’s worth revisiting those topics first. A detailed explanation is outside the scope of this article.
Price Level Abstration
A price level consists of:
- A fixed price
- A queue of orders at that price, ordered by time priority
template <int Price, typename OrderQueue>
struct Level
{
static constexpr int price = Price;
using orderQueue = OrderQueue;
};
template<int Price>
using EmptyLevel = Level<Price, Queue<>>;
This gives us a simple compile-time representation of a price level.
Adding an Order to a Level
Adding an order means appending it to the end of the queue.
template <typename Level, typename Order>
struct AddOrderToLevel
{
using _newQueue = QueuePush_t<typename Level::orderQueue, Order>;
using type = ::Level<Level::price, _newQueue>;
};
template<typename Level, typename Order>
using AddOrderToLevel_t = typename AddOrderToLevel<Level, Order>::type;
Breaking intermediate results into named aliases (like _newQueue) greatly improves readability in TMP-heavy code. You can think of them as variables in a functional program.
A Subtle but Important TMP Detail
You might have noticed the use of both ::Level<...> and Level::<field> in the previous section. At first glance, this syntax can look confusing — and that’s completely understandable.
You might reasonably expect the following to work:
using type = ::Level<Level::price, _newQueue>;
However, compiling this results in the following error:
error: 'Level' is not a template
29 | using type = Level<Level::price, _newQueue>;
| ^~~~~
Why? Within this scope:
Levelrefers to the type passed as a template argument- It does not refer to the
Leveltemplate itself
When the compiler encounters Level<...>, it tries to treat Level as a template. But in this context, Level is already a concrete type, so instantiating it as a template is invalid.
This distinction — between a template name and a type name — is subtle and easy to miss, especially in TMP-heavy code. It’s worth re-reading this carefully; almost everyone trips over this at least once.
Now the question is how do we disambiguate and tell the compiler where to expect a type and where to expect a template.
How Do We Disambiguate?
One way to achieve this is to change the name of the template argument.
template<int OrderId, typename L>
struct RemoveOrderFromLevel
{
using type = Level<L::price, RemoveOrderFromQueue_t<OrderId, typename L::orderQueue>>;
};
This works because Level now clearly refers to the template, and L refers to the type. However, renaming template parameters like this is not ideal. When writing generic code, it’s often clearer and safer to name parameters after the concepts they represent.
Global Scope Resolution (::)
A better and more explicit solution is to use the global scope resolution operator ::.
Here’s what this tells the compiler:
::Level<...>→ instantiate the Level template from the global scopeLevel::price→ access a member of the Level type passed as a template argument
Once you internalize this distinction, the pattern becomes very natural — and you’ll start spotting it throughout advanced TMP code and the standard library itself.
Removing an Order from a Level
Order removal is more interesting.
Unlike a regular queue, we must be able to remove an element from an arbitrary position based on orderId.
Removing an Order from the Queue
template<int OrderId, typename Queue>
struct RemoveOrderFromQueue;
template<int OrderId>
struct RemoveOrderFromQueue<OrderId, Queue<>>
{
using type = Queue<>;
};
template<int OrderId, typename O1, typename... O2toN>
struct RemoveOrderFromQueue<OrderId, Queue<O1, O2toN...>>
{
using remainingType =
typename RemoveOrderFromQueue<OrderId, Queue<O2toN...>>::type;
using type = std::conditional_t<
O1::orderId == OrderId,
remainingType,
QueueMerge_t<Queue<O1>, remainingType>
>;
};
template<int OrderId, typename Queue>
using RemoveOrderFromQueue_t =
typename RemoveOrderFromQueue<OrderId, Queue>::type;
This is a classic recursive template algorithm:
- Process the queue element by element
- Recursively compute the remaining queue
- Use
std::conditional_tto decide:
- Skip the current order if it matches
- Otherwise, prepend it back
This pattern — recurse, then conditionally rebuild — appears repeatedly in TMP.
Removing an Order from a Level
template<int OrderId, typename Level>
struct RemoveOrderFromLevel
{
using type = ::Level<
Level::price,
RemoveOrderFromQueue_t<OrderId, typename Level::orderQueue>
>;
};
template<int OrderId, typename Level>
using RemoveOrderFromLevel_t =
typename RemoveOrderFromLevel<OrderId, Level>::type;
Notice the use of ::Level and Level::<field> at appropriate places.
Key Takeaways
- Compile-time queues are naturally expressed using variadic templates
- Recursive TMP often follows a process → recurse → rebuild pattern
std::conditional_tis the TMP equivalent ofif / else- Template parameters and templates themselves live in different namespaces
::is a powerful tool for disambiguation in complex TMP code
What’s Next
With price levels implemented, we can now:
- Create levels
- Add and cancel orders while preserving time priority
In Part 4, we will build a ListOfLevels abstraction that:
- Maintains levels sorted by price
- Supports insertion and removal
- Implements a compile-time merge sort
It’s one of the most interesting parts of the series — and likely not something you’ve encountered before.
Implementing merge sort entirely at compile time pushes your understanding of C++ templates well beyond what’s expected in most C++ interviews.
Part 4 — Merge Sort and List of Price Levels
메타데이터
- post_id
- 1908e6f10cb3
- slug
- compile-time-limit-order-book-in-c-price-levels-and-compile-time-queues-part-3-1908e6f10cb3
- url
- https://medium.com/@rishabhgarg108/compile-time-limit-order-book-in-c-price-levels-and-compile-time-queues-part-3-1908e6f10cb3
- canonical_url
- https://medium.com/@rishabhgarg108/compile-time-limit-order-book-in-c-price-levels-and-compile-time-queues-part-3-1908e6f10cb3
- author_url
- https://medium.com/@rishabhgarg108
- status
- ok
- fetched_at
- 2026-08-22 20:24:00