Perfect Forwarding in C++11

Posted: July 22, 2012 in C++
Tags: , ,

In the last post we discussed about RValue references in C++11.The next logical step will be to move forward to the forwarding problem.This is related to forwarding or passing arguments from one C++ method to another. Refer to the code snippet below.

template <typename T> struct S;

template <typename T> struct S { 
    static void f2(T t) {

        cout << "Within f2(T t)" <<endl; 
    } 
};

template <typename T> void f1(T t) { 
    cout << "Within f1"<<endl;; 
    S<T&&>::f2(t); 
};

int main() 
{ 
    string a("10"); 
    f1(a); 
    return 0; 
}

The output of this program will be:

Within f1
Within f2(T t)

Here we are doing a pass by value and hence one extra copy of the parameters will be created. To avoid this we can do a pass by reference as shown below.

template <typename T> struct S;

template <typename T> struct S<T&> { 
    static void f2(T& t) {

        cout << "Within f2(T& t)" <<endl; 
    } 
};

template <typename T> void f1(T& t) { 
    cout << "Within f1"<<endl;; 
    S<T&>::f2(t); 
};

int main() 
{ 
    string a("10"); 
    f1(a); 
    return 0; 
}

The output of this program will be:

Within f1
Within f2(T& t)

But this will lead to a problem with the following call:

 
int main() 
{ 
    const int i=10; 
    f1<int>(i); 
    return 0; 
} 

This gives a compilation error:

error: no matching function for call to ‘f1(const int&)’

This is because the parameter of f1 does not accept constant references. We can resolve this problem by defining an additional overload as shown below:

   
template <typename T> struct S;

template <typename T> struct S<T&> { 
    static void f2(T& t) {

        cout << "Within f2(T& t)" <<endl; 
    } 
}; 
template <typename T> struct S<const T&>{ 
    static void f2(const T& t) {

    cout << "Within f2(const T& t)" <<endl; 
    } 
};

template <typename T> void f1(T& t) { 
    cout << "Within f1"<<endl;; 
    S<T&>::f2(t); 
}; 
template <typename T> void f1(const T& t) { 
    cout << "Within f1"<<endl;; 
    S<const T&>::f2(t); 
};

int main() 
{ 
const int i=10; 
f1<int>(i); 
return 0; 
} 

But this will actually lead to a bloated code as the number of parameters increases.This can be solved with rvalue references introduced in C++11 as shown below:

 
template <typename T> struct S;

template <typename T> struct S<T&> 
{ 
    static void f2(T& t) 
    {

        cout << "Within f2(T& t)" <<endl; 
    } 
}; 
template <typename T> struct S<const T&> 
{ 
    static void f2(const T& t) 
    {

        cout << "Within f2(const T& t)" <<endl; 
    } 
};

template <typename T> void f1(T&& t) 
{ 
    cout << "Within f1"<<endl;; 
    S<T>::f2(t); 
};

int main() 
{ 
    string a("10"); 
    const int i=10; 
    f1(a); 
    f1(i); 
    return 0; 
}

This is possible because of the rvalue references and the template argument type deduction and reference collapsing rules in C++11.

If we pass an rvalue reference of type A to a template function with parameter T&& then A&& && is deduced to be A&&.

If we pass an lvalue/const reference of type A to a template function with parameter T&& then const A& && or A& && is deduced to be const A& or A&.

The output of the program is:

Within f1
Within f2(T& t)
Within f1
Within f2(const T& t)

But when we will try to pass

 
f1(10); 

It leads to a compilation error:

incomplete type ‘S<int>’ used in nested name specifier

For we need to change S<T> to S<T&> as shown below in the call to f1.

 
template <typename T> void f1(T&& t) 
{ 
    cout << "Within f1"<<endl;; 
    S<T&>::f2(t); 
};

Now the program output changes to:

Within f1
Within f2(T& t) //
Within f1
Within f2(const T& t)
Within f1
Within f2(T& t)

Let’s add two more overload for f2 with T&& and const T&& as parameters for rvalue references

 
template <typename T> struct S;

template <typename T> struct S<T&> 
{ 
    static void f2(T& t) 
    {

        cout << "Within f2(T& t)" <<endl; 
    } 
}; 
template <typename T> struct S<const T&> 
{ 
    static void f2(const T& t) 
    {

        cout << "Within f2(const T& t)" <<endl; 
    } 
};

template <typename T> struct S<T&&> 
{ 
    static void f2(T&& t) 
    { 
        cout << "Within f2(T&& t)" <<endl; 
    } 
}; 
template <typename T> struct S<const T&&> 
{ 
    static void f2(const T&& t) 
    {

        cout << "Within f2(const T&& t)" <<endl; 
    } 
};

template <typename T> void f1(T&& t) 
{ 
    cout << "Within f1"<<endl;; 
    S<T&>::f2(t); 
};

int main() 
{ 
    string a("10"); 
    const int i=10; 
    f1(a); 
    f1(i); 
    f1(10); 
    return 0; 
}


The output still remains the same:

Within f1
Within f2(T& t)
Within f1
Within f2(const T& t)
Within f1
Within f2(T& t)

The last call was expected to print "Within f2(T&& t)"  as it is a rvalue reference.So this is not perfect forwarding, as the rvalue/lvalue/const nature of the parameters are not properly preserved.Both named rvalue and lvalue references are treated as lvalue references by the compiler.So we need a mechanism which helps to us preserve the original nature of the arguments passed.This is what the function std::forward does.

template <typename T> struct S;

template <typename T> struct S<T&> 
{ 
    static void f2(T& t) 
    {

        cout << "Within f2(T& t)" <<endl; 
    } 
}; 
template <typename T> struct S<const T&> 
{ 
    static void f2(const T& t) 
    {

        cout << "Within f2(const T& t)" <<endl; 
    } 
};

template <typename T> struct S<T&&> 
{ 
    static void f2(T&& t) 
    { 
        cout << "Within f2(T&& t)" <<endl; 
    } 
}; 
template <typename T> struct S<const T&&> 
{ 
    static void f2(const T&& t) 
    {

        cout << "Within f2(const T&& t)" <<endl; 
    } 
};

template <typename T> void f1(T&& t) 
{ 
    cout << "Within f1"<<endl;; 
    S<T&&>::f2(std::forward<T>(t)); 
};

int main() 
{ 
    string a("10"); 
    const int i=10; 
    f1(a); 
    f1(i); 
    f1(10); 
    return 0; 
}

The output is as expected:

Within f1
Within f2(T& t)
Within f1
Within f2(const T& t)
Within f1
Within f2(T&& t)

This is all about forwarding, in the next post we will discuss about move semantics.

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