C/C++ Preprocessor Metaprogramming - Evaluating and Defering Macro Calls

Before we go into our more in depth examples, I want to provide some basic building block macros.

Deferring/Evaluating Expressions

Defering and evaluating macros are going to be used a lot and are really important to be discussed. The goal of defer is to delay the evaluation of a macro. Evaluation macros are going to evaluate macros. We will make use of combinations them heavily to achieve many things. Lets first look at what the use case of the macro will be

#define FOO(x, y) x y
#define BAR() x, y

/*
  The following expression results in an error
  This is because FOO is expanded first
  As a result, Since Foo only has 1 parameter Bar
  It complains we didnt provide the correct arguments
*/
FOO(BAR()) 

/*
  Using defer allows BAR() to be evaluated first.
  However, since we used the defer, FOO is not evaluated
*/
DEFER(FOO) (BAR()) // Expands to FOO(x, y)

/*
  To get around this, we will need an evaluation macro.
  Calling EVAL will allow us to evalated the DEFER expression
*/
EVAL(DEFER(FOO) (BAR())) // Expands to x y

/*
  We might also want to defer twice
  if we have two levels of evaluation needed
*/
#define QUX() BAR()

// Error: The following
// expands to FOO(BAR()) and FOO is evaluated
EVAL(DEFER(FOO) (QUX())) 

EVAL(DEFER2(FOO) (QUX())) // Expands to x y

The definition of DEFER and EVAL is actually relatively simple, we can make use of our rules discussed previously to make it work. The idea of DEFER is to require n evaluations for the deferred expression/macro to actually get called. We can use our EMPTY macro that we first saw in the rules section. By adding EMPTY() calls in between a macro call and its parameters we can defer the execution of FOO by however many EMPTY's we have.

// EMPTY() expands to nothing
#define EMPTY() 

FOO(x, y) // x y
// We require an extra evaluation to evaluate FOO
FOO EMPTY() (x, y) // FOO(x, y)
// We require two extra evaluations to evaluate FOO
FOO EMPTY EMPTY() () (x,y ) // FOO EMPTY() (x, y)

This pattern can be implemented in the DEFER macros.

// __VA_ARGS__ is the expression to defer
#define DEFER(...) __VA_ARGS__ EMPTY()
// To defer it twice we simply add in another defered EMPTY() call
#define DEFER2(...) __VA_ARGS__ DEFER(EMPTY) ()
#define DEFER3(...) __VA_ARGS__ DEFER2(EMPTY) ()
#define DEFER4(...) __VA_ARGS__ DEFER3(EMPTY) ()
#define DEFER5(...) __VA_ARGS__ DEFER4(EMPTY) ()
// .
// .
// .
// Can have this go to however many defer stages you need
// This can easily be autogen'd

Next, we want to implement an EVAL macro which evaluates the macro. This will come later when we explore recursion but the main goal is we want to have a sub-expression evaluated many times. The way this works is by relying on Rule 3. By having an empty macro that simply returns our arguments we can have an expression evaluated once. Stack the calls to these macros and we can evaluate many more times. If we have an EVAL call another EVAL twice and have that EVAL call another EVAL, we can have 2^n evaluations of an expression where n is how many times we stack. This is easier to show in code

#define EVAL_1(...) __VA_ARGS__
// Note how we call EVAL of the lower level twice
// This allows us to double the number of EVAL calls per level
#define EVAL_2(...) EVAL_1(EVAL_1(__VA_ARGS__))
#define EVAL_3(...) EVAL_2(EVAL_2(__VA_ARGS__))
#define EVAL_4(...) EVAL_3(EVAL_3(__VA_ARGS__))
#define EVAL_5(...) EVAL_4(EVAL_4(__VA_ARGS__))
#define EVAL_6(...) EVAL_5(EVAL_5(__VA_ARGS__))
#define EVAL_7(...) EVAL_6(EVAL_6(__VA_ARGS__))
#define EVAL_8(...) EVAL_7(EVAL_7(__VA_ARGS__))
// Finally our EVAL calls the top level EVAL call
#define EVAL(...) EVAL_8(__VA_ARGS__)
// ...
// This can easily  autogen'd

Note, if have a macro that is evaluated using EVAL which calls another macro that uses an EVAL then that EVAL wont be expanded (Rule 4). This can be seen in the following example.

#define BAR(x) EVAL(x)
EVAL( DEFER(BAR)(x) ) // Expands to EVAL(x)

We would need to create an appropriate EVAL chain for macro if they are going to be chained together

// Assuming EVALA and EVALB are defined appropriately
#define BAR(x) EVALA(x)
EVALB( DEFER(BAR)(x) ) // Expands to x

C/C++ Preprocessor Metaprogramming - Applications: Typedef Unrolling

Another use for metaprogramming is when you want to have multiple typedef for variable parameters. An example of this is say you have a Vector class and we want typdef it for various dimensions and various data types.

template<int NUM_DIMENSIONS, class DataType>
struct Vector {
  DataType m_data[NUM_DIMENSIONS;
};

typedef Vector<2, int> Vector2i;
typedef Vector<2, double> Vector2d;
typedef Vector<2, float> Vector2f;

typedef Vector<3, int> Vector3i;
typedef Vector<3, double> Vector3d;
typedef Vector<3, float> Vector3f;

Suppose we wanted to expand this for more dimensions and data types as well. We can make use of pattern matching and FOR_EACH_2D to make our life easier.

// Pattern match PREFIX so that we can get a prefix if appropriate
// Otherwise return the type itself
#define PREFIX_int EXISTS(i)
#define PREFIX_float EXISTS(f)
#define PREFIX_double EXISTS(d)
#define GET_PREFIX(type) TRY_EXTRACT_EXISTS(CAT(PREFIX_, type), type)

#define DEFINE_VECTOR_TYPE(n, type) \
  typedef Vector<n, type> CAT(CAT(Vector, n), GET_PREFIX(type));

FOR_EACH_2D(DEFINE_VECTOR_TYPE, (2, 3, 4), (int, double, float))
/* Expands to
  typedef Vector<2, int> Vector2i;
  typedef Vector<2, double> Vector2d;
  typedef Vector<2, float> Vector2f;

  typedef Vector<3, int> Vector3i;
  typedef Vector<3, double> Vector3d;
  typedef Vector<3, float> Vector3f;

  typedef Vector<4, int> Vector4i;
  typedef Vector<4, double> Vector4d;
  typedef Vector<4, float> Vector4f;
*/
#undef DEFINE_VECTOR_TYPE

C/C++ Preprocessor Metaprogramming - Applications: Dynamic Template Parameters

So with all these features, lets actually look at some examples. The first example I want to go into is dynamic template parameters (for a limited range of parameters).

Motivation

Lets say you have a template that has some template parameter N that you wish to manipulate (with a defined values) at run time. An example of this is something like this is

template<int N> SomeReturnType Foo(SomeParams)
{
  // Do something with with our paramters and N and return something
}
int main()
{
  int n;
  std::cin >> n;
  // Limit n to a certain amount of values
  Foo<N>(params);
}

The first question you might ask is why? If N is dynamic, why not just pass it in as a parameter? One of the biggest reasons is performance (assuming this is critical to the function). By providing N as a template parameter, the compiler can optimize the function call for that value N by unrolling loops, restructuring code, memory optimization, etc.

A personal use case for this was trying to implement image processing convolutions on a GPU for many of my projects. I wanted to have dynamically sized convolution kernals that could be adjusted at compile time. Comparing the performance of using simulated dynamic template parameters vs dynamic width parameters resulted in optimizations of 2-10x processing time reduction (especially when my widths were small).

Another good example of this is the FFT Example by Dr Dobbs where by using the length as a template parameter, and template metaprogramming, a recursive FFT can have really nice performance!

Simulating dynamic parameters

The best way to simulate this is by having a switch statement that compares the dynamic parameter and calls the appropriately template parameter. Ie FooDynamic(n) calls FooTemplate (). An example of this is

template<int N> SomeReturnType FooDynamic(n, parameters)
{
  if ( n == 1 ) FooTemplate<1> (parameters);
  if ( n == 2 ) FooTemplate<2> (parameters);
  if ( n == 3 ) FooTemplate<3> (parameters);
  .
  .
  .
}

Note, X Macros are a perfectly good solution to this. However we can use FOR_EACH as well. Furthermore, with range we can avoid having to write our values explicitly. Lets say we wanted to allows the parameters from n = 1 to 10. The following code becomes

template<int N> SomeReturnType FooDynamic(n, parameters)
{
#define DYNAMIC_TEMPLATE_CALL(x) if (n == x) FooTemplate<x> (parameters);
  // Expands to if statements for all the values
  FOR_EACH(DYNAMIC_TEMPLATE_CALL, RANGE(1, 10))
#undef DYNAMIC_TEMPLATE_CALL
}