Template Meta-programming in C++

Template Meta-programming in C++

What it is not

It is not some magic wand that will tell your C++ expertise. Many of us don't feel the need to use meta-programming just because we don't generally write library code or generic code in a day-to-day routine.

Like think of it, when have you last written some generic entity/class/functionality that can operate on different data types. Maybe you have, but most of us don't.

So if you don't know it, don't let your peers bully you just because of that.

What it is

But if you know it, you can do all sorts of crazy things that feel kind of magic. Its like when you think

this function should not be called on this type, doesn't the compiler already know that and it can chose not to call this function

Yes the compiler knows the type, but its "us" that need to tell the compiler what to do. Like you know this function should only be called on pointer types, and for other types, it should be invisible. Yes you can do that.

It is also all done in the compilation step, there is no overhead on the runtime of the program if you use some complex template construct.

But yes I can bet one thing on that. IT LOOKS COOL!

Creating our own "trait"

Yes they are called "traits". The properties that a type holds is called a trait.

A Simple compile time check of type

This is not some magic. Let us build our own trait named "is_pointer".

Normal definition of our type "is_pointer"

So "is_pointer" is nothing but just a struct, with a templated parameter "T", that holds a enum with a member "value" that is always "false".

If now we call

Evaluates to false

So whatever type we create a struct for, value == false. But here comes the interesting part, we will SPECIALIZE the struct for the pointer type. Like this

Specializtion for pointer types

Now this is a specialized struct that will be only instantiated when you give it a pointer type. See the value == true for all the cases where T is a pointer.

So now if we do

Evaluates to true

The program successfully compiles and the assertion is true.

Was it really that difficult? I don't think so.

Template Meta-programming is nothing but use of some of the specialized structs that are meant for some specific types. In this case "pointer" types.

Another chapter "SFINAE"

No it is not a mis-spelled word. It's literally called "sfinae".

Substitution Failure Is Not An Error. SFINAE.

But what are we substituting? Where is it? And what is not error? Yes C++ standards committee is sometimes pretty bad towards naming things. So whenever you define a function, let's say you have some overload of a function named "print", that is supposed to print some data onto stdout. So when you call the function named print(), the compiler generates a what is known as a "candidate set" of functions that the print is supposed to be called on.

So when we have some templated function that is specialized for some of the types, the candidate set is created during the compile time. And when you call that function, the compiler tries to match which of the candidate functions correctly match with the parameters that you are calling, this is called SUBSTITUTION.

And when a candidate function is rejected due to some mismatched types, it is called a SUBSTITUTION FAILURE.

Let me tell you with the above example.

So now can you read it? Let me help you,

  1. "print" is a function that takes a type "T". Now there are two typenames, that does not mean you need to give two types to call this function. One of the type is T, and the other type is deduced by the compiler. It will be clear below.
  2. Whenever you want the compiler to deduce some type from some other type, you use the keyword "typename". So the first typename on the second line denotes that we are trying to define a template parameter, but it has not name. It an un-named template parameter.
  3. The "=" denotes that we want to give a default type to the template parameter, which is explained below.
  4. So if you read the inner trait, "is_integral", it is very clear that it checks if the type T is an integer type (int, long). You may also try to write your own "is_integral" type, just for some fun. I know now you get this concept. Okay let's move along.
  5. There is another trait called "enable_if". So what it does is you give it a boolean value in the first template parameter and a type in the second template parameter. And if the boolean type is "true type" then it has a member called "type" that holds the type "T" that you give in the second parameter. Seems unnecessary right? Yes but it has more power than we can imagine. It's really fun.
  6. So see this example

Use of print with a double type

Remember we have only defined the "print" function for the integral types, for all other types this function is not a candidate function, lets see the error that we get.

Compiler error

It says "no matching function call to print(double).

But SFINAE right, failure is not an error, so why does this error comes?

So you need to have a default print() function that can be called with some other type, maybe you can ignore the implementation. So in that case this specialized version for the integral types will not be a candidate function but the other default one. So this is where substitution failure occurred but it does not give an error, but choses the default type.


It takes a while to get used to, but its really fun when you try to practise meta-programming writing different traits.

What else?

Well we haven't even touched the tip of the iceberg. There is a lot more to it than this blog post can do justice. But let me show you one use case for SFINAE just to do some interesting things.

Example types

There are two classes, one that has a print() function, and one that does not.

So what we want to do is

create a generic print() function, that can take any type "T" that has a "print()" member function in it

So we can leverage SFINAE here with some other traits to have this behaviour.

print function that takes types that contain print() function in them

So here we can read it like

  1. print takes one argument of some type T, that is described below
  2. The compiler then tries to obtain a return type of "T().print()" using "decltype". So if some class T does not have the print() function then this function will be automatically removed from the candidate set. Simple.


There is a lot more to meta-programming that I can cover here in a blog. Do reach me out for some discussions if you are interested. Feel free to comment and correct if you see anything incorrect in this post.

Happy Coding!


要查看或添加评论,请登录

Shantanu Banerjee的更多文章

  • Improving std::for_each

    Improving std::for_each

    With C++11 we get the `std::for_each` for enumerating the iterators of a container. It is a useful helper that you…

社区洞察

其他会员也浏览了