December 24, 2016

Today, we solve ” … a herefore unsolved problem in C++” (Bjarne Stroustrup). To make the long story short, I will write about perfect forwarding.

 

But what is perfect forwarding?

If a function templates forward its arguments without changing its lvalue or rvalue characteristics, we call it perfect forwarding.

Great. But what are lvalues and rvalues? Now, I have to make a little detour.

Lvalues and Rvalues

I will not discuss the details about lvalues and rvalues and introduce, therefore glvalues, xvalues ,and prvalues. That’s not necessary. In case, you are curious, read the post from Anthony Williams: Core C++ – lvalues and rvalues. I will provide in my post a sustainable intuition.

Rvalues are

  • temporary objects.
  • objects without names.
  • objects which have no address.

If one of the characteristics holds for an object, it will be an rvalue. In reverse, that means that lvalues have a name and an address. Here are a few examples for rvalues:

int five= 5;
std::string a= std::string("Rvalue");
std::string b= std::string("R") +  std::string("value");
std::string c= a + b;
std::string d= std::move(b);

Rvalues are on the right side of an assignment. The value 5 and the constructor call are std::string(“Rvalue”) rvalues because you can neither determine the address of the value 5 nor has the created string object a name. The same holds for the addition of the rvalues in the expression std::string(“R”) + std::string(“value”).

The addition of the two strings a + b is interesting. Both strings are lvalues, but the addition creates a temporary object. A particular use case is std::move(b). The new C++11 function converts the lvalue b into an rvalue reference.

Rvalues are on the right side of an assignment; lvalues can be on the left side of an assignment. But that is not always true:

const int five= 5;
five= 6;

 

Although variable five is an lvalue. But five is constant, and you can not use it on the left side of an assignment.

But now to the challenge of this post: Perfect forwarding. To get an intuition for the unsolved problem, I will create a few perfect factory methods.

A perfect factory method

First, a short disclaimer. The expression a perfect factory method is no formal term.

A perfect factory method is, for me, a generic factory method. In particular, that means that the function should have the following characteristics:

  • It can take an arbitrary number of arguments
  • Can accept lvalues and rvalues as an argument
  • Forwards its arguments identical to the underlying constructor

I want to say it less formally. A perfect factory method should be able to create each arbitrary object.

Let’s start with the first iteration.

First iteration

For efficiency reasons, the function template should take its arguments by reference. To say it exactly. As a non-constant lvalue reference. Here is the function template create in my first iteration. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// perfectForwarding1.cpp

#include <iostream>

template <typename T,typename Arg>
T create(Arg& a){
  return T(a);
}


int main(){
    
  std::cout << std::endl;

  // Lvalues
  int five=5;
  int myFive= create<int>(five);
  std::cout << "myFive: "  << myFive << std::endl;

  // Rvalues
  int myFive2= create<int>(5);
  std::cout << "myFive2: " << myFive2 << std::endl;

  std::cout << std::endl;

}

 

If I compile the program, I will get a compiler error. The reason is that the rvalue (line 21) can not be bound to a non-constant lvalue reference.

perfectForwarding1

Now, I have two ways to solve the issue.

  1. Change the non-constant lvalue reference (line 6) in a constant lvalue reference. You can bind an rvalue to a constant lvalue reference. But that is not perfect because the function argument is constant, and I can not change it.
  2. Overload the function template for a constant and non-const lvalue reference. That is easy. That is the right way to go.

Second iteration

Here is the factory method create overloaded for a constant lvalue reference and a non-constant lvalue reference.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// perfectForwarding2.cpp

#include <iostream>

template <typename T,typename Arg>
T create(Arg& a){
  return T(a);
}

template <typename T,typename Arg>
T create(const Arg& a){
  return T(a);
}

int main(){
    
  std::cout << std::endl;

  // Lvalues
  int five=5;
  int myFive= create<int>(five);
  std::cout << "myFive: "  << myFive << std::endl;

  // Rvalues
  int myFive2= create<int>(5);
  std::cout << "myFive2: " << myFive2 << std::endl;

  std::cout << std::endl;

}

 

The program produces the expected result.

perfectForwarding2

That was easy. Too easy. The solution has two conceptual issues.

  1. To support n different arguments, I have to overload  2^n +1 variations of the function template create. 2^n +1 because the function create without an argument is part of the perfect factory method.
  2. The function argument mutates in the function body of creating an lvalue, because it has a name. Does this matter? Of course, yes. a is not movable anymore. Therefore, I must perform an expensive copy instead of a cheap move. But what is even worse? If the T (line 12) constructor needs an rvalue, it will no longer work.

 

Now, I have the solution in the shape of the C++ function std::forward.

Third iteration

With std::forward, the solution looks promising.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// perfectForwarding3.cpp

#include <iostream>

template <typename T,typename Arg>
T create(Arg&& a){
  return T(std::forward<Arg>(a));
}

int main(){
    
  std::cout << std::endl;

  // Lvalues
  int five=5;
  int myFive= create<int>(five);
  std::cout << "myFive: "  << myFive << std::endl;

  // Rvalues
  int myFive2= create<int>(5);
  std::cout << "myFive2: " << myFive2 << std::endl;

  std::cout << std::endl;

}

 

Before I present the recipe from cppreference.com to get perfect forwarding, I will introduce the name universal reference.

The name universal reference is coined by Scott Meyers.

The universal reference (Arg&& a) in line 7 is powerful and can bind lvalues or rvalues. If you declare a variable Arg&& a for a derived type A, you have it at your disposal.

To achieve perfect forwarding, you must combine a universal reference with std::forward. std::forward<Arg>(a) returns the underlying type because a is a universal reference. Therefore, an rvalue remains an rvalue.

Now to the pattern

template<class T>
void wrapper(T&& a){
    func(std::forward<T>(a)); 
}
 

I used the color red to emphasize the critical parts of the pattern. I used exactly this pattern in the function template create. Only the name of the type changed from T to Arg.

Is the function template create perfect? Sorry to say, but now. create needs precisely one argument, which is perfectly forwarded to the object’s constructor (line 7). The last step is to make a variadic template out of the function template.

Fourth iteration – the perfect factory method

Variadic Templates are templates that can get an arbitrary number of arguments. That is precisely the missing feature of the perfect factory method.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// perfectForwarding4.cpp

#include <iostream>
#include <string>
#include <utility>

template <typename T, typename ... Args>
T create(Args&& ... args){
  return T(std::forward<Args>(args)...);
}

struct MyStruct{
  MyStruct(int i,double d,std::string s){}
};

int main(){
    
    std::cout << std::endl;

  // Lvalues
  int five=5;
  int myFive= create<int>(five);
  std::cout << "myFive: "  << myFive << std::endl;

  std::string str{"Lvalue"};
  std::string str2= create<std::string>(str);
  std::cout << "str2: " << str2 << std::endl;

  // Rvalues
  int myFive2= create<int>(5);
  std::cout << "myFive2: " << myFive2 << std::endl;

  std::string str3= create<std::string>(std::string("Rvalue"));
  std::cout << "str3: " << str3 << std::endl;

  std::string str4= create<std::string>(std::move(str3));
  std::cout << "str4: " << str4 << std::endl;
  
  // Arbitrary number of arguments
  double doub= create<double>();
  std::cout << "doub: " << doub << std::endl;
  
  MyStruct myStr= create<MyStruct>(2011,3.14,str4);


  std::cout << std::endl;

}

 

The three dots in lines 7 -9 are the so-called parameter pack. If the three dots (also called ellipse) are left of Args, the parameter pack will be packed; if right, the parameter pack will be unpacked. In particular, the three dots in line 9 std std::forward<Args>(args)… causes each constructor call to perform perfect forwarding. The result is impressive. Now, I can invoke the perfect factory method without (line 40) or with three arguments (line 43).

 perfectForwarding4

What’s next?

 RAII, short for Resource Acquisition Is Initialization is a very important idiom in C++. Why? Read in the next post. 

 

 

 

 

 

 

Leave a Reply


Your email address will not be published. Required fields are marked

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

Related Posts