Using Higher-Order Macros in C to implement Monomorphisation
One of C++s most-often-cited advantages (over any language) is it ability to monomorphise abstracted calls via templates – to reuse code without the overhead of indirection and virtual method calls.
As a part of the language, the only mainstream language that supports this feature is C++, although several novel languages, like Haskell and Rust, support it too. This is claimed to account for much of its speed advantage over managed languages, such as Java, in abstracted code.
Because of this, I was disappointed when it turned out that I have to write my supposed-to-be-fast alpha-beta implementation in C for my software project in a generic manner, which would seemingly require a virtual call every node and probably an allocation (because different boards have different sizes).
I thought about this problem and I think I found a solution. Before presenting it, I would like to describe a few quite-common C preprocessor tricks.
Trick 1 – foreach in C:
This is a quite old trick that can be used to implement foreach in C – the oldest use I found is in BSD’s queue.h
I think the best way to introduce it is by example – which iterates over a list-style linked list.
Here, the user provides a variable of the appropriate type as cur, and name should hold the input list and the macro does the rest.
Trick 2 – higher order macros:
If you pass the name of a functional macro to a C preprocessor macro, the last macro can pass it’s own parameters.
So:
Prints 42.
Now for the negamax implementation:
Textbook negamax/alpha-beta looks like this (credit: Wikipedia):
function pvs(node, depth, α, β, color)
if node is a terminal node or depth = 0
return color × the heuristic value of node
for each child of node
score := -pvs(child, depth-1, -β, -α, -color)
α := max(α, score)
if α ≥ β
break (* beta cut-off *)
return α
If your game implementation keeps the board internally, and cloning boards does not require allocations, you can implement FOREACH_CHILD in the following manner:
You can implement alpha-beta by basically copying from the textbook (in a separate header), like this:
Note that I use INT_MIN as a sentinel score to avoid needing to check if moves were made. Then it can be used like this:
Which I think is quite elegant, actually.
Next: how I am planning to implement virtual calls.