perfectForwarding1

Perfect Forwarding

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.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

Be part of my mentoring programs:

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (starts March 2024)
  • Do you want to stay informed: Subscribe.

     

    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. 

     

     

     

     

     

     

    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, 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, Marco Parri Empoli, moon, Philipp Lenk, Hobsbawm, and Charles-Jianye Chen.

    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
    My special thanks to SHAVEDYAKS

    Seminars

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

    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++
    • C++20

    Online Seminars (German)

    Contact Me

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

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