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.
- 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:
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:
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.
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.
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.
Now, I have two ways to solve the issue.
- 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.
- Overload the function template for a constant and non-const lvalue reference. That is easy. That is the right way to go.
Here is the factory method create overloaded for a constant lvalue reference and a non-constant lvalue reference.
The program produces the expected result.
That was easy. Too easy. The solution has two conceptual issues.
- 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.
- 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.
With std::forward, the solution looks promising.
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
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.
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).
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, 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, Rob North, Bhavith C Achar, and Marco Parri Empoli.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
|My special thanks to Embarcadero|
|My special thanks to PVS-Studio|
|My special thanks to Tipi.build|
|My special thanks to Take Up Code|
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
- Embedded Programmierung mit modernem C++ 12.12.2023 – 14.12.2023 (Präsenzschulung, Termingarantie)
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++
- Clean Code with Modern C++
- Phone: +49 7472 917441
- Mobil:: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,