10 min read

The simplest way to say this is that metaprogramming is a technique that creates a code by using a code. Implementing metaprogramming, we write a computer program that manipulates the other programs and treats them as its data. In addition, templates are a compile-time mechanism in C++ that is Turing-complete, which means any computation expressible by a computer program can be computed, in some form, by a template metaprogram before runtime. It also uses recursion a lot and has immutable variables. So, in metaprogramming, we create code that will run when the code is compiled.

This tutorial is an excerpt taken from the book,’ Learning C++ Functional Programming‘, written by Wisnu Anggoro. In this book, you’ll learn to apply Functional Programming techniques to C++ to build highly modular, testable, and reusable code. In this article, we will learn how to build Template Metaprogramming (TMP) in C++.

Preprocessing the code using a macro

To start our discussion on metaprogramming, let’s go back to the era when the ANSI C programming language was a popular language. For simplicity, we used the C preprocessor by creating a macro. The C parameterized macro is also known as metafunctions and is one of the examples of metaprogramming. Consider the following parameterized macro:

    #define MAX(a,b) (((a) > (b)) ? (a) : (b))

Since the C++ programming language has a drawback compatibility with the C language, we can compile the preceding macro using our C++ compiler. Let’s create the code to consume the preceding macro, which will be as follows:

    /* macro.cpp */
    #include <iostream>

using namespace std;

// Defining macro

#define MAX(a,b) (((a) > (b)) ? (a) : (b))

auto main() -> int
{
cout << "[macro.cpp]" << endl;

// Initializing two int variables
int x = 10;
int y = 20;

// Consuming the MAX macro
// and assign the result to z variable
int z = MAX(x,y);

// Displaying the result
cout << "Max number of " << x << " and " << y;
cout << " is " << z << endl;

return 0;
}

As we can see in the preceding macro.cpp code, we pass two arguments to the MAX macro since it is a parameterized macro, which means the parameter can be obtained from the users. If we run the preceding code, we should see the following output on the console:

Metaprogramming is a code that will run in compile time. By using a macro in the preceding code, we can demonstrate there’s a new code generated from the MAX macro. The preprocessor will parse the macro in compile time and bring the new code. In compile time, the compiler modifies the code as follows:

    auto main() -> int
    {
      // same code
      // ...
int z = (((a) > (b)) ? (a) : (b)); // <-- Notice this section

// same code
// ...

return 0;
}

Besides a one-line macro preprocessor, we can also generate a multiline macro metafunction. To achieve this, we can use the backslash character at the end of the line. Let’s suppose we need to swap the two values. We can create a parameterized macro named SWAP and consume it like the following code:

/* macroswap.cpp */
    #include <iostream>
using namespace std;

// Defining multi line macro

#define SWAP(a,b) { \
(a) ^= (b); \
(b) ^= (a); \
(a) ^= (b); \
}

auto main() -> int
{
cout << "[macroswap.cpp]" << endl;

// Initializing two int variables
int x = 10;
int y = 20;

// Displaying original variable value
cout << "before swapping" << endl;
cout << "x = " << x << ", y = " << y ;
cout << endl << endl;

// Consuming the SWAP macro
SWAP(x,y);

// Displaying swapped variable value
cout << "after swapping" << endl;
cout << "x = " << x << ", y = " << y;
cout << endl;

return 0;
}

As we can see in the preceding code, we will create a multiline preprocessor macro and use backslash characters at the end of each line. Each time we invoke the SWAP parameterized macro, it will then be replaced with the implementation of the macro. We will see the following output on the console if we run the preceding code:

Now we have a basic understanding of the metaprogramming, especially in metafunction, we can move further in the next topics.

We use parenthesis for each variable in every implementation of the macro preprocessor because the preprocessor is simply replacing our code with the implementation of the macro. Let’s suppose we have the following macro:
MULTIPLY(a,b) (a * b)
It won’t be a problem if we pass the number as the parameters. However, if we pass an operation as the argument, a problem will occur. For instance, if we use the MULTIPLY macro as follows:
MULTIPLY(x+2,y+5);
Then the compiler will replace it as (x+2*y+5). This happens because the macro just replaces the a variable with the x + 2 expression and the b variable with the y + 5 expression, with any additional parentheses. And because the order of multiplication is higher than addition, we will have got the result as follows:
(x+2y+5)
And that is not what we expect. As a result, the best approach is to use parenthesis in each variable of the parameter.

Dissecting template metaprogramming in the Standard Library

The Standard Library provided in the C++ language is mostly a template that contains an incomplete function. However, it will be used to generate complete functions. The template metaprogramming is the C++ template to generate C++ types and code in compile time.

Let’s pick up one of the classes in the Standard Library–the Array class. In the Array class, we can define a data type for it. When we instance the array, the compiler actually generates the code for an array of the data type we define. Now, let’s try to build a simple Array template implementation as follows:

    template<typename T>
    class Array
    {
      T element;
    };

Then, we instance the char and int arrays as follows:

    Array<char> arrChar;
    Array<int> arrInt;

What the compiler does is it creates these two implementations of the template based on the data type we define. Although we won’t see this in the code, the compiler actually creates the following code:

    class ArrayChar
    {
      char element;
    };
class ArrayInt
{
int element;
};

ArrayChar arrChar;
ArrayInt arrInt;

As we can see in the preceding code snippet, the template metaprogramming is a code that creates another code in compile time.

Building the template metaprogramming

Before we go further in the template metaprogramming discussion, it’s better if we discuss the skeleton that builds the template metaprogramming. There are four factors that form the template metaprogramming–type, value, branch, and recursion. In this topic, we will dig into the factors that form the template.

Adding a value to the variable in the template

In the macro preprocessor, we explicitly manipulate the source code; in this case, the macro (metafunction) manipulates the source code. In contrast, we work with types in C++ template metaprogramming. This means the metafunction is a function that works with types. So, the better approach to use template metaprogramming is working with type parameters only when possible. When we are talking about the variables in template metaprogramming, it’s actually not a variable since the value on it cannot be modified. What we need from the variable is its name so we can access it. Because we will code with types, the named values are typedef, as we can see in the following code snippet:

    struct ValueDataType
    {
      typedef int valueDataType;
    };

By using the preceding code, we store the int type to the valueDataType alias name so we can access the data type using the valueDataType variable. If we need to store a value instead of the data type to the variable, we can use enum so it will be the data member of the enum itself. Let’s take a look at the following code snippet if we want to store the value:

    struct ValuePlaceHolder
    {
      enum 
       { 
        value = 1 
       };
    };

Based on the preceding code snippet, we can now access the value variable to fetch its value.

Mapping a function to the input parameters

We can add the variable to the template metaprogramming. Now, what we have to do next is retrieve the user parameters and map them to a function. Let’s suppose we want to develop a Multiplexer function that will multiply two values and we have to use the template metaprogramming. The following code snippet can be used to solve this problem:

    template<int A, int B>
    struct Multiplexer
    {
      enum 
      {
        result = A * B 
      };
    };

As we can see in the preceding code snippet, the template requires two arguments, A and B, from the user, and it will use them to get the value of result variable by multiplying these two parameters. We can access the result variable using the following code:

    int i = Multiplexer<2, 3>::result;

If we run the preceding code snippet, the i variable will store 6 since it will calculate 2 times 3.

Choosing the correct process based on the condition

When we have more than one function, we have to choose one over the others based on certain conditions. We can construct the conditional branch by providing two alternative specializations of the template class, as shown here:

    template<typename A, typename B>
    struct CheckingType
    {
      enum 
      { 
        result = 0 
      };
    };
template<typename X>
struct CheckingType<X, X>
{
enum 
{ 
result = 1 
};
};

As we can see in the preceding template code, we have two templates that have X and A/B as their type. When the template has only a single type, that is, typename X, it means that the two types (CheckingType <X, X>) we compare are exactly the same. Otherwise, these two data types are different. The following code snippet can be used to consume the two preceding templates:

    if (CheckingType<UnknownType, int>::result)
    {
      // run the function if the UnknownType is int
    } 
    else 
    { 
      // otherwise run any function 
    }

As we can see in the preceding code snippet, we try to compare the UnknownType data type with the int type. The UnknownType data type might be coming from the other process. Then, we can decide the next process we want to run by comparing these two types using templates.

Up to here, you might wonder how template multiprogramming will help us make code optimization. Soon we will use the template metaprogramming to optimize code. However, we need to discuss other things that will solidify our knowledge in template multiprogramming. For now, please be patient and keep reading.

Repeating the process recursively

We have successfully added value and data type to the template, then created a branch to decide the next process based on the current condition. Another thing we have to consider in the basic template is repeating the process. However, since the variable in the template is immutable, we cannot iterate the sequence.
Let’s suppose we are developing a template to calculate the factorial value. The first thing we have to do is develop a general template that passes the I value to the function as follows:

    template <int I>
    struct Factorial
    {
      enum 
      { 
        value = I * Factorial<I-1>::value 
      };
    };

As we can see in the preceding code, we can obtain the value of the factorial by running the following code:

    Factorial<I>::value;

In the preceding code, I is an integer number.
Next, we have to develop a template to ensure that it doesn’t end up with an infinite loop. We can create the following template that passes zero (0) as a parameter to it:

    template <>
    struct Factorial<0>
    {
      enum 
      { 
        value = 1 
      };
    };

Now we have a pair of templates that will generate the value of the factorial in compile time. The following is a sample code to get the value of Factorial(10) in compile time:

    int main()
    {
      int fact10 = Factorial<10>::value;
    }

If we run the preceding code, we will get 3628800 as a result of the factorial of 10.

Thus, in this post, we learned how to build Template Metaprogramming with the C++ programming language. If you’ve enjoyed reading this post and want to know more about Flow control with template metaprogramming and more, do check out the book, Learning C++ Functional Programming.

Read Next

Introduction to R Programming Language and Statistical Environment

Use Rust for web development [Tutorial]

Boost 1.68.0, a set of C++ source libraries, is released, debuting YAP!

A Data science fanatic. Loves to be updated with the tech happenings around the globe. Loves singing and composing songs. Believes in putting the art in smart.