Kami
Kami

Reputation: 1109

Move semantics and perfect forwarding difference

I already got what move semantics is from this question: What are move semantics?

But I still do not get what perfect forwarding is in relation to move semantics.

Can someone explain in simple english and with a simple example what perfect forwarding means?

Upvotes: 10

Views: 4087

Answers (2)

Niall
Niall

Reputation: 30606

Dealing with r-value references and reference collapsing can be more complex than it initially appears.

Perfect forwarding

Perfect forwarding is there to ensure that the argument provided to a function is forwarded (passed) to another function with the same value category (basically r-value vs l-value) as originally provided.

It is typically used with template functions where reference collapsing may have taken place.

It can also be used within the same function.

Scott Meyers gives the following pseudo code in his Going Native 2013 presentation to explain the workings of std::forward (at approximately the 20 minute mark);

template <typename T>
T&& forward(T&& param) { // T&& here is formulated to disallow type deduction
  if (is_lvalue_reference<T>::value) {
    return param; // return type T&& collapses to T& in this case
  }
  else {
    return move(param);
  }
}

Example

An example from the site above, an archetypical example is that of make_unique

template<class T, class... U>
std::unique_ptr<T> make_unique(U&&... u)
{
    return std::unique_ptr<T>(new T(std::forward<U>(u)...));
}

In the example, the arguments for the unique_ptr are provided to it through the make_unique as if they had been provided directly to unique_ptr, i.e. the reference, l-value and r-value nature of the arguments are maintained.

A more concrete example;

#include <iostream>
#include <utility>
#include <memory>

struct A {
  // implementation excluded
};

struct B {
  B(A &) // ctor 1
  {
    std::cout << "ctor 1" << std::endl;
  }
  B(A&&) // ctor 2
  {
    std::cout << "ctor 2" << std::endl;
  }
};

int main()
{
  A a;
  auto b1 = std::make_unique<B>(a); // ctor 1 is used
  auto b2 = std::make_unique<B>(A()); // ctor 2 is used
}

In Brief

Perfect forwarding depends on a handful of fundamental language constructs new to C++11 that form the bases for much of what we now see in generic programming:

  • Reference collapsing
  • Rvalue references
  • Move semantics

The use of std::forward is currently intended in the formulaic std::forward<T>, understanding how std::forward works helps understand why this is such, and also aids in identifying non-idiomatic or incorrect use of rvalues, reference collapsing and ilk.

Thomas Becker provides a nice, but dense write up on the perfect forwarding problem and solution.

Upvotes: 3

Marco A.
Marco A.

Reputation: 43662

Plain English-only attempt

The problem is probably too complex to be accurately described by plain English sentences, but one could think of perfect forwarding as a way to move temporary values passed to a function to another one as if the first function didn't exist at all, so without any unnecessary copies or assignments. C++11 allows you to do this by introducing some conversion rules between r-value (&&) and l-value (&) references to a type when you try to get a reference (either r-value or l-value) out of them.

R-value references are a feature of C++11 and they were designed to both address move semantics and perfect forwarding issues

This is the plain-English explanation but if you want to thoroughly understand the problem, I'd suggest reading the following:

The problem:

We want some temporary values passed to a function F to be passed to another one E without any copy or assignment.

Attempts to solve this issue

  • If you try to pass it by reference like

    template<typename T> void F(T& a) { E(a); }
    

    you will not be able to use temporaries (they're not l-values)

    F(1, 2, 3); // Won't work
    
  • Declaring a reference as const prolongs the lifetime of a temporary on the stack (this was historically done to avoid a common dangling reference error) so the following works

    template<typename T> void E(const T& a) {}
    
    template<typename T> void F(const T& a) {
        E(a);
    }
    

    but the downside is that you'll have to modify the signature of the function(s) to conform to this solution

  • If we're interested in the signature of E (it should conform to something) but not in F's one, we might get away with

    template<typename T> void E(T& a) {}
    
    template<typename T> void F(const T& a) {
        E(const_cast<T&>(a));
    }
    

    but in case this gets called with a real const and gets un-constant'ed, that would trigger undefined behavior

  • An unmaintainable solution could be to define all the variants you need

    template<typename T> void E(T& a) {}
    
    template<typename T> void F(T& a) { E(a); }
    template<typename T> void F(const T& a) { E(const_cast<T&>(a)); }
    

    but as the number of parameters grow, the number of combinations grows as well: this is likely to become unmaintainable

The solution in C++11

C++11 defines some rules that state

"[given] a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR."

in human-form (TR = a reference to type T, R = reference):

TR      R
T& & -> T&    // an lvalue reference to cv TR (becomes)-> lvalue reference to T 
T& && -> T&   // an rvalue reference to cv TR (becomes)-> TR (lvalue reference to T) 
T&& & -> T&   // an lvalue reference to cv TR (becomes)-> lvalue reference to T 
T&& && -> T&& // an rvalue reference to cv TR (becomes)-> TR (rvalue reference to T)

The important takeaway here is that now you can keep track of the type the function received: you can receive an l-value and pass the same l-value to E or you can receive an r-value and pass the same r-value (after converting it since an l-value reference to whatever type reference becomes an l-value reference) to E:

template<typename T> void E(T&& a) {}

template<typename T> void F(T&& a) { E(static_cast<T&&>(a)); }

A synctactic sugar for

static_cast<T&&>(a)

is

std::forward<T>(a); // is the same as static_cast<T&&>(a);

so the final code that solves the problem and makes your life easier is

template<typename T> void E(T&& a) {}

template<typename T> void F(T&& a) { E(std::forward<T>(a)); }

Live example


References: Herb Sutter's blog and some other sources which unfortunately I can't find anymore. If anyone has a clue about those please write them in the comments below and I'll update the post. Thanks.

Upvotes: 16

Related Questions