The following function template invoke (as its name says) invokes the function/functor/lambda expression passed as an argument, passing it the two extra arguments given:
#include <iostream>
#include <string>
void sum(int a, int b)
{
std::cout << a + b << std::endl;
}
void concat(const std::string& a, const std::string& b)
{
std::cout << a + b << std::endl;
}
template <typename PROC, typename A, typename B>
void invoke(PROC p, const A& a, const B& b)
{
p(a, b);
}
int main()
{
invoke(sum, 10, 20);
invoke(concat, "Hello ", "world");
return 0;
}
Nice, it works as expected, and the result is:
30
Hello world
The problem with that implementation is that it only works with arguments passed as constant references, so if the programmers try to invoke the following function:
void successor(int a, int& b)
{
b = a + 1;
}
with this call:
int s = 0;
invoke(successor, 10, s);
std::cout << s << std::endl;
the compiler returns this error in the invoke implementation:
Binding of reference to type 'int' to a value of type 'const int' drops qualifiers
This error occurs because the second argument of the successor function is not a const-reference.
Before C++11, the only way to deal with this problem was creating a set of overloads containing all the possible combinations of const, non-const references in the methods, something like this:
#include <iostream>
#include <string>
void sum(int a, int b)
{
std::cout << a + b << std::endl;
}
void concat(const std::string& a, const std::string& b)
{
std::cout << a + b << std::endl;
}
void successor(int a, int& b)
{
b = ++a;
}
template <typename PROC, typename A, typename B>
void invoke(PROC p, const A& a, const B& b)
{
p(a, b);
}
template <typename PROC, typename A, typename B>
void invoke(PROC p, const A& a, B& b)
{
p(a, b);
}
template <typename PROC, typename A, typename B>
void invoke(PROC p, A& a, const B& b)
{
p(a, b);
}
template <typename PROC, typename A, typename B>
void invoke(PROC p, A& a, B& b)
{
p(a, b);
}
int main()
{
invoke(sum, 10, 20);
invoke(concat, "Hello", "world");
int s = 0;
invoke(successor, 10, s);
std::cout << s << std::endl;
return 0;
}
Notice that there are four overloads for the invoke function template example above because two parameters need to be forwarded to the function passed in P.
If the function had more parameters, this would be unmaintainable (for N arguments, programmers would need to have 2^N overloads because of all the const/non-const possible combinations).
Starting from C++11, C++ lets programmers perform perfect forwarding, which means that they can forward the parameters passed to a function template to another function call inside it without losing their own qualifiers (const-ref, ref, value, rvalue, etc.).
To use this technique, the invoke function template in the example must have arguments passed as rvalue references, and the std::forward function template, which is in charge of performing the type deduction and forwarding the arguments to the invoked function with its own reference, const-reference, or rvalue-reference qualifiers, must be used. Using this technique, the code becomes something like this:
#include <iostream>
#include <string>
#include <utility>
void sum(int a, int b)
{
std::cout << a + b << std::endl;
}
void concat(const std::string& a, const std::string& b)
{
std::cout << a + b << std::endl;
}
void successor(int a, int& b)
{
b = ++a;
}
template <typename PROC, typename A, typename B>
void invoke(PROC p, A&& a, B&& b)
{
p(std::forward<A>(a), std::forward<B>(b));
}
int main()
{
invoke(sum, 10, 20);
invoke(concat, "Hello", "world");
int s = 0;
invoke(successor, 10, s);
std::cout << s << std::endl;
return 0;
}
What is this technique used for?
For example, consider this class:
struct A { };
struct B { int a; int b; };
struct C { int a; int b; string c; };
struct D
{
public:
D(int a) {}
};
Programmers want to have a factory function template that will let them run this code:
int main()
{
auto a = factory<A>();
auto b = factory<B>(10, 20);
auto c = factory<C>(30, 40, "Hello");
auto d = factory<D>(10);
std::cout << c->c << std::endl;
}
As can be seen, the factory function template must be a variadic-template-based function, so a solution would be this one:
template <typename T, typename ... ARGS>
std::unique_ptr<T> factory(const ARGS&... args)
{
return std::unique_ptr<T>{new T { args... }};
}
Notice that the std::make_unique<T> helper template method works like this example and serves the same purpose: create a new std::unique_ptr of type T, forwarding the parameters passed to its constructor.
Nice, and it works as expected. But the problem of losing qualifiers arises again. What happens with the following code?
If programmers try to use this struct in this way:
int main()
{
int x = 2;
auto e = factory<E>(x);
std::cout << x << std::endl;
}
They will get an error because x is an int&, not a const int&.
Fortunately, perfect forwarding is the way to go:
#include <iostream>
#include <string>
#include <utility>
struct A { };
struct B { int a; int b; };
struct C { int a; int b; string c; };
struct D
{
public:
D(int a) {}
};
struct E
{
int& e;
E(int& e) : e{e} { e++; }
};
template <typename T, typename ... ARGS>
std::unique_ptr<T> factory(ARGS&&... args)
{
return std::unique_ptr<T>{new T { std::forward<ARGS>(args)... }};
}
int main()
{
auto a = factory<A>();
auto b = factory<B>(10, 20);
auto c = factory<C>(30, 40, "Hello");
auto d = factory<D>(10);
int x = 2;
auto e = factory<E>(x);
std::cout << x << std::endl;
}
Perfect forwarding also helps avoid writing several overloads of functions to support move semantics and copy semantics.
For example:
#include <iostream>
struct X
{
X() { std::cout << "ctor" << std::endl; }
X(const X&) { std::cout << "copy ctor" << std::endl; }
X(X&&) { std::cout << "move ctor" << std::endl; }
};
struct Wrapper
{
X w;
Wrapper(const X& w) : w{w} { }
};
int main()
{
Wrapper w1{X {}};
std::cout << "***" << std::endl;
X y;
Wrapper w2{y};
}
This produces the output:
ctor
copy ctor
***
ctor
copy ctor
The little problem here is that when w1 is being constructed, the instance of X is a temporary instance that will be destroyed immediately after invoking the Wrapper constructor. In this case, invoking the move constructor should be better (performance-wise).
So, the programmers can create a second constructor and modify the Wrapper class to be like this:
struct Wrapper
{
X w;
Wrapper(const X& w) : w{w} { }
Wrapper(X&& w): w{std::move(w)} { } // Adding a overloaded constructor
};
Cool! It works as expected:
ctor
move ctor
***
ctor
copy ctor
When constructing w1, the Wrapper constructor with an rvalue reference parameter is used. In the example with a constructor that takes one parameter, this works nicely and is easy to maintain, but with constructors or functions that take many parameters, the same maintainability issues mentioned above arise again. So, programmers can rely on perfect forwarding to let the compiler decide which constructor to use and generate the required (and desired) code:
struct Wrapper
{
X w;
template <typename Q>
Wrapper(Q&& w) : w{std::forward<Q>(w)} { }
};
Nice, isn’t it?
std::forward<T> is declared in the header file <utility>