Perfect Forwarding

Contents[Show]

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.

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Be part of my mentoring programs:

 

 

 

 

Do you want to stay informed about my mentoring programs: Subscribe via E-Mail.

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. 

 

 

 

 

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, and Rob North.

 

Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, and Slavko Radman.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

My special thanks to Tipi.build tipi.build logo

 

My special thanks to Take Up Code TakeUpCode 450 60

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

  • C++ - The Core Language
  • C++ - The Standard Library
  • C++ - Compact
  • C++11 and C++14
  • Concurrency with Modern C++
  • Design Pattern and Architectural Pattern with C++
  • Embedded Programming with Modern C++
  • Generic Programming (Templates) with C++

New

  • Clean Code with Modern C++
  • C++20

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

 

Tags: move

Comments   

+1 #51 Rainer Grimm 2020-08-16 20:09
Quoting Vaibhav Rajgolkar:
Hello Reiner,

This is one of the best C++ tutorial. I enjoy reading your posts. One small typo error that may be confusing sometimes. In the sentence "The value 5 and the constructor call are std::string("Rvalue") rvalues because you can either determine the address of the value 5 nor has the created string object a name." it should be neither-nor combination. Ofcourse everybody can make out the meaning from sentence but sometimes confuses if new to C++.

Thanks and Regards,
Vaibhav Rajgolkar

Thanks a lot, fixed it.
Quote
0 #52 Ankur Sharma 2021-06-11 18:04
Wow!
I loved the way you have structured the the content. The next time some one asks me to explain perfect forwarding, I am redirecting them to this page :)
Quote
0 #53 John 2022-03-18 03:21
ellipsis, not ellipse
Quote

Stay Informed about my Mentoring

 

Mentoring

English Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Course: The All-in-One Guide to C++20

Course: Master Software Design Patterns and Architecture in C++

Subscribe to the newsletter (+ pdf bundle)

All tags

Blog archive

Source Code

Visitors

Today 1879

Yesterday 4344

Week 38757

Month 19003

All 12097212

Currently are 152 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments