C++11: Perfect forwarding

Consider this function template invoke that invokes the function/functor/lambda expression passed as argument passing it the two extra arguments given:

#include <iostream>
#include <string>

using namespace std;

void sum(int a, int b)
{
    cout << a + b << endl;
}

void concat(const string& a, const string& b)
{
    cout << a + b << 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 my implementation is that it only works with arguments passed as constant references, so if I would like to invoke this function:

void successor(int a, int& b)
{
    b = a + 1;
}

with this call:

int s = 0;
invoke(successor, 10, s);
cout << s << endl;

clang returns me 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 my successor function is not a const-reference.

Before C++11 the only way to deal with this problem was to create a set of overloads containing all the possible combinations of const, non-const references in the methods, something like this:

#include <iostream>
#include <string>

using namespace std;

void sum(int a, int b)
{
    cout << a + b << endl;
}

void concat(const string& a, const string& b)
{
    cout << a + b << 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);
    cout << s << endl;
    return 0;
}

Notice I had to implement four overloads for my invoke function template because I need to forward two parameters to the function passed in P.

If I would have more parameters, this would be unmaintainable (for N arguments, I would need to have 2^N overloads).

C++11 lets us perform perfect forwarding, which means that we 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.).

In order to use this technique, my invoke function template must have arguments passed as rvalue references and I need to use the std::forward function template that is in charge of performing the type deduction and forward the arguments to the invoked function with its own reference, const-reference or rvalue-reference qualifiers. Look into my code modified using this technique:

#include <iostream>
#include <string>

using namespace std;

void sum(int a, int b)
{
    cout << a + b << endl;
}

void concat(const string& a, const string& b)
{
    cout << a + b << 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);
    cout << s << endl;
    return 0;
}

What I would want to use this technique for?

Consider having these classes:

struct A { };
struct B { int a; int b; };
struct C { int a; int b; string c; };
struct D
{
public:
    D(int a) {}
};

I want to have a factory function template that will let me 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);
    
    cout << c->c << endl;
    
}

As you can see, the factory function template must be a variadic-template based function, so, a solution would be this one:

template <typename T, typename ... ARGS>
unique_ptr<T> factory(const ARGS&... args)
{
    return unique_ptr<T>(new T { args... });
}

Nice, and it works as expected. But the problem of losing qualifiers arises again. What if I have something like:

struct E
{
    int& e;
    E(int& e) : e(e) { e++; }
};

If you try to use this struct in this way:

int main()
{
    int x = 2;
    auto e = factory<E>(x);
    cout << x << endl;  
}

You 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>

using namespace std;

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>
unique_ptr<T> factory(ARGS&&... args)
{
    return 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);
    cout << x << endl;
}

Perfect forwarding also helps to avoid to write several overloads of functions to support move semantics and copy semantics.

For example, look at this code:

#include <iostream>

using namespace std;

struct X
{
    X() { cout << "ctor" << endl; }
    X(const X&) { cout << "copy ctor" << endl; }
    X(X&&) { cout << "move ctor" << endl; }
};

struct Wrapper
{
    X w;
    
    Wrapper(const X& w) : w(w) { }
};

int main()
{
    Wrapper w1(X { });
    cout << "***" << endl;
    X y;
    Wrapper w2(y);
}

The output is:

ctor
copy ctor
***
ctor
copy ctor

The little problem here is that when constructing w1, the instance of X is a temporary instance that will be destroyed immediately after invoking the Wrapper constructor. For this case, invoking the move constructor should be better (performance-wise).

So, we can create a second constructor and modify our Wrapper class to be like this one:

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 that has a rvalue reference parameter is used. In my example with a constructor with one parameter, this work nice and is easy to maintain, but with constructors or functions that have a lot of parameters, the same maintainability issues shown above raise again. So, we can one more time rely on perfect forwarding to let the compiler decide what 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)) { }
};

It’s nice, isn’t it?

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s