Template Meta Programming (TMP) - A perfect forwarder



Templates are useful and powerful tools to write generic code. Templates enable Template Meta Programming (TMP) with a set of utility functions in STL. One common aspect you will often come across while writing generic code with templates is a forwarding function. In this blog, we will quickly look at writing a generic forwarder – a perfect forwarder!


If we observe carefully, we see most of the STL algorithms which accept functors or predicates or lambdas do perfect forwarding. Like this code below will print each element of the vector with lambda function passed to it.


std::vector<int> v = { 1, 2, 3, 4, 5 };

for_each( v.begin(), v.end(), [] (int v){ std::cout << v; } );


Now internally for_each is essentially forwarding enumeration call to lambda function we passed in. But will this work if we have more than one element to pass to the callee? Not with for_each above but it is equally easy to write your version of for_each for such cases which do forwarding. We will call it for_eachv2. Here’s one way to do it:


Output:

Process: Single param:1

Process: Single param:2

Process: Single param:3

Process: Single param:4

Process: Single param:5

Process: Double param:1 10

Process: Double param:2 10

Process: Double param:3 10

Process: Double param:4 10

Process: Double param:5 10

Process: Triple param:1 10 20

Process: Triple param:2 10 20

Process: Triple param:3 10 20

Process: Triple param:4 10 20

Process: Triple param:5 10 20


What is happening here? std::invoke is very handy forwarder from your generic code. It can forward calls to any predicates or lambdas or functors. So now our for_eachv2 can call any variadic argument predicate!


Sometimes you will need an ability to call any variadic functor from your generic code. For such cases, std::forward should suffice on its own. Here’s an example:

Output:

PerfectForwarderSingleParam

callee: Single param:1

PerfectForwarderMultiParam

callee: Single param:1

PerfectForwarderMultiParam

callee: Double param:1 2

PerfectForwarderMultiParam

callee: Triple param:1 2 3


In this example, we have a perfect forwarder code inside the template. Here we have a function named ‘callee’ which is overloaded for one, two and three parameters respectively. Inside generic/template code, we want to be able to call a function overload matching the passed number of arguments and so the overloaded signature. Above code will exactly do that.


std::invoke has a basic syntax as follows and it is added from C++17:

std::invoke will also accept class member function too! When passed function is a class member function, then std::invoke will use a zeroth argument as ‘this’ argument, so you have to make sure to pass that. Here’s for_eachv3 version of our template which can now accept class member functions as callable.


Output:

Process: Single param:1

Process: Single param:2

Process: Single param:3

Process: Single param:4

Process: Single param:5

Process: Double param:1 10

Process: Double param:2 10

Process: Double param:3 10

Process: Double param:4 10

Process: Double param:5 10

Process: Triple param:1 10 20

Process: Triple param:2 10 20

Process: Triple param:3 10 20

Process: Triple param:4 10 20

Process: Triple param:5 10 20

Myclass::Process 1

Myclass::Process 2

Myclass::Process 3

Myclass::Process 4

Myclass::Process 5



Summary:


You can use std::forward to forward any number of arguments to any selected function. This allows you to write variadic templates (which take a variable number of arguments). This is useful in many contexts such as calling constructors or independent function overloads from the generic template code.


You can use std::invoke to call any function from the template code without knowing the exact function signature. std::invoke will also accept class member functions as callable, but to do that you have to make sure zeroth member in argument list passed is a class object.


std::invoke and std::forward can be used together, and when you do it allows you to create a perfect forwarder code from the generic template code.