DEV Community

loading...

C++ Meta Programming: Why?

koralkashri profile image Koral Kashri Originally published at cppsenioreas.wordpress.com on ・4 min read

As the time goes by, C++ meta programming features keep increase and improve. Every release adds more and more meta programming features, although you can write a complete program without using it even once. So why does this topic draw so much attention, and why does it considered as one of the most powerful tools of C++?

This article is the first in a series of articles about meta programming in C++. The examples in this article are just for demonstration of the abilities of metaprogramming in C++, and some of them won't get explain here, but in future articles.

Next post on series: Basic templates usage – Part 1

Meta Programming VS. Generic Programming

Most of the high-level programmers, who go lower into C++, usually refer to C++ templates as "Generic Programming", because in high level languages like Java or C#, the expression <T> means a generic parameter. In C++, calling templates "Generic Programming" simply means underestimation of templates. This topic is far bigger, more complex, and full of possibilities to just call it "Generic".

Let’s start with a simple example of templates:

template <typename T>
class Foo {
public:
    explicit Foo(T val) : val(val) {}
    T val;
};
Enter fullscreen mode Exit fullscreen mode

So far it doesn’t look so much more than a “Generic” class. We can call it like this:

Foo<int> f(5);
Enter fullscreen mode Exit fullscreen mode

Or like this:

Foo f(5);
Enter fullscreen mode Exit fullscreen mode

Wait, Can we just throw away the template specification? Sometimes the answer for this question is Yes. This feature called template argument deduction, and on the basic level, it means that whenever the compiler can deduce the types at the compilation time, it will, in order to save you a hard-coding time, or to avoid complex code, as much as possible (don't worry, it won't stay that easy for much longer).

Let’s take this Foo class one step forward:

void foo_func(std::string s) {
    std::cout << "Foo Func: " << s << std::endl; // Think about this line for a moment... Can you see the deduction here? Even in the first "Hello World" program in C++ we are using template parameters deduction.
}

// ... main ...

Foo f2(foo_func);
f2.val("A");
Enter fullscreen mode Exit fullscreen mode

No, it's still the same Foo class from before, no change at all, the deduction here is: void(*)(std::string) - a function pointer. But, deduction isn't enough to convince anyone to stop calling C++ templates as "Generic Programming", so take a deep breath, we are going deeper.

Compile time values calculation

Meta programming become the run-times ultimate saver - anything we can compute at the compilation time, we'll do it there, and with the most readable way we can.

const my_highly_important_value = 4194304;
Enter fullscreen mode Exit fullscreen mode

Some of you might guess it's not a random value and might even recognize it as the exact result of 2 ^ 22. Yes, I could add a comment or give it a more informative name, but I could also do something like that:

// some math library
template <size_t Base, size_t N>
struct pow {
    static constexpr long long val = Base * pow<Base, N - 1>::val;
};

template <size_t Base>
struct pow<Base, 0> {
    static constexpr long long val = 1;
};

// ...

const my_highly_important_value = math::pow<2, 22>;
Enter fullscreen mode Exit fullscreen mode

For the full explanation & example about how this works there will be a future post, a simple explanation for now: A recursion on compile time to compute the desire value. About the run time, there is no different at all between const my_highly_important_value = 4194304; and const my_highly_important_value = math::pow<2, 22>; but we can clearly see which one is better.

Explicit template functions specialization

Another possibility that gave us the meta programming is the explicit (full) template functions specializations. Assume you have a function that accepts a single template parameter. Your function knows how to deal with any numeric type, but you have a special implementation for floats. You know a way to make this function run faster if it get to handle floats, but you still don't want to eliminate the rest of the types implementation. Two ways to handle this situation:

  1. Create a different function with a different name for floats.
  2. Use explicit template specialization/constexper conditions for float cases.

If you had decided to be nicer to your team, you would have probably chosen the second option (but I don't judge for choosing the easy way, it's still legal to make mistakes). Since C++17, you can use constexpr conditions:

template <typename T>
void function(T type) {
    if constexpr (std::is_same_v<T, float>) {
        std::cout << type << " is float!" << std::endl;
    } else {
        std::cout << type << " is not a float!" << std::endl; 
    }
}
Enter fullscreen mode Exit fullscreen mode

And once again - the condition is on compilation time, no extended run time needed.

And Much More…

All these features and we didn't talk about template inheritances, variadic templates, template conditions (extended version of the last example), constexpr functions, auto, and more...

Conclusion

There are much more in templates than a "Generic Programming" especially since C++17/20, and we'll talk about it in future posts in this series. Please comment if you have specific subjects you'd like to see in the future posts and describe your experience with meta programming.

This post originally published on my personal blog: C++ Senioreas.

Discussion (0)

pic
Editor guide