Document number: N2951=09-0141

Howard E. Hinnant
2009-09-27

forward

Introduction

There is a fundamental disagreement in the design philosophy over std::forward. One camp believes that forward should only be used in a very specific pattern, namely:

template <class T>
void f(T&& x)
{
    // x is an lvalue here.  If the actual argument to f was an
    // rvalue, pass static_cast<T&&>(x) to g; otherwise, pass x.
    g( forward<T>(x) );
}

and all other uses by our customers should be forbidden.

The fundamental disagreement is that I do not believe it is possible, or even a good idea, to try to define the semantics of a function by attempting to limit its use to a very specific coding pattern. Instead a function's design should be based on its inputs, outputs and side effects. With this particular function there should be no side effects, so we only need to decide what inputs are appropriate and what the corresponding output should be.

We, as a committee, are not sufficiently prescient to foresee all of the ingeniously clever applications any tool might be used for.

Where would C++ be today if its design had followed a philosophy that there was only one right way to code, and all other styles should be shunned, and outright prevented where possible? I believe this would be a very different (and inferior) language today had the committee had such a mindset in the 1990's.

Use cases backed up by testing of those use cases should drive a function's interface design. Which use cases provide beneficial behavior? Which are likely to lead to accidental run time errors? Which use cases provide both benefit and risk, and does the benefit outweigh the risk?

This paper reviews six proposed implementations of std::forward (from both sides of the disagreement) and tests each of them against six use cases. Some of those use cases will lead to dangling references (which is bad). And some of the use cases will offer truly useful and safe functionality. This paper will favor those implementations of std::forward which allow the client maximum utility while giving compile time diagnostics which prevent the use cases which would otherwise lead to run time errors.

The conclusions of this paper are previewed here for those who prefer to just "get to the point". The use cases are labeled A - F, and the implementations 0 - 5. Case 0 is equivalent to just using static_cast<T&&> (which has been suggested). Only implementation 5 of std::forward passes all use cases without error, and is strongly recommended by this paper:

forward implementation results for each use case
      ABCDEFscore
0 pass  pass  rfail  pass  pass  rfail 67%
1 pass  cfail  rfail  pass  pass  rfail 50%
2 pass  cfail  pass  cfail  cfail  pass 50%
3 pass  pass  pass  pass  pass  rfail 83%
4 pass  pass  pass  pass  cfail  pass 83%
5 pass  pass  pass  pass  pass  pass 100%
cfail
A compile time failure.
rfail
A run time failure.

Terminology

std::forward<T>(t) is, at the end of the day, a static_cast<T&&>(t). Nothing more, and possibly less. And the question really is: How much less should it be?

To describe the domain and range of this function, this paper will use terms such as:

forward an lvalue t as an rvalue T

In code, this means:

static_cast<T&&>(t);

where t is known to be an lvalue expression, and T is known to be an object or function type (not a reference type). In general, the type of t need not be the same as the type of T. This paper will explore that aspect with use cases as well.

Forwarding an lvalue t as an lvalue T is quite similar:

static_cast<T&>(t);

t might also represent an rvalue expression, in which case we then speak of forwarding an rvalue t as an lvalue or rvalue T (again using the same static_cast expressions).

There is nothing in this paper that is any deeper than these static_cast expressions.

forward Implementations

There are six forward implementations explored in this paper and they will be labeled 0 thru 5. I've chosen 0 to be an implementation which most closely mimics the behavior of forward we voted into the WP in April 2007 in Oxford. All implementations presented herein reflect the language rules in effect since after the July 2009 Frankfurt meeting (no concepts, lvalues can not implicitly bind to an rvalue reference).

  1. A base case reformulated without identity, and reflecting the design of forward up until the language change prohibiting lvalues from implicitly binding to rvalue references (but reformulated to work with that language change). Note that this choice is also equivalent to the suggestion "just use static_cast<T&&>."

    template <class T, class U>
    inline
    T&&
    forward(U&& u)
    {
        return static_cast<T&&>(u);
    }
    
  2. Suggestion 1 given by David Abrahams in the 2009-08-02 comment of LWG 1054 with the following rationale:

    Use a simple definition of forward that accomplishes its original purpose without complications to accomodate other uses.
    template <class T, class U>
    T&& forward(U& u)
    {
        return static_cast<T&&>(u);
    }
    
  3. Suggestion 2 given by David Abrahams in the 2009-08-02 comment of LWG 1054 with the following rationale:

    Use a definition of forward that protects the user from as many potential mistakes as possible, by actively preventing all other uses.
    template <class T, class U>
    boost::enable_if_c<
        is_lvalue_reference<U>::value
        && !is_rvalue_reference<T>::value
        && is_same<T&,U&>::value
        , T&&>::type forward(U&& a)
    {
        return static_cast<T&&>(a);
    }
    
  4. An implementation that allows forwarding an rvalue as an rvalue, but not an rvalue as an lvalue, and places no restriction on type conversions.

    template <class T, class U,
        class = typename enable_if<
             is_lvalue_reference<T>::value ?
                 is_lvalue_reference<U>::value :
                 true
        >::type>
    inline
    T&&
    forward(U&& u)
    {
        return static_cast<T&&>(u);
    }
    

    Note: This constraint is equivalent to that shown in my 2009-08-02 comment of LWG 1054. This is just another way to write it.

  5. Similar to #3 but with additional constraints preventing base type conversions (like #2), but it doesn't prevent adding cv-qualifiers (unlike #2). This is also what I say is my preferred solution in the 2009-08-02 comment of LWG 1054. However after Jason expressed some concerns and I investigated/tested the use cases Jason described, I have become convinced the next design is better.

    template <class T>
    struct __base_type
    {
        typedef typename remove_cv<typename remove_reference<T>::type>::type type;
    };
    
    template <class T, class U,
        class = typename enable_if<
             (is_lvalue_reference<T>::value ?
                 is_lvalue_reference<U>::value :
                 true) &&
             is_same<typename __base_type<T>::type,
                     typename __base_type<U>::type>::value
        >::type>
    inline
    T&&
    forward(U&& u)
    {
        return static_cast<T&&>(u);
    }
    
  6. Similar to #4 but not as restrictive. It allows any conversion in which a temporary is not created as a result of the conversion. This includes a derived to base conversion (credit Jason Merrill for the problem identification).

    template <class T, class U,
        class = typename enable_if<
             (is_lvalue_reference<T>::value ?
                 is_lvalue_reference<U>::value :
                 true) &&
             is_convertible<typename remove_reference<U>::type*,
                            typename remove_reference<T>::type*>::value
        >::type>
    inline
    T&&
    forward(U&& u)
    {
        return static_cast<T&&>(u);
    }
    

Emphasis: Every implementation simply does a static_cast<T&&>(u). The only question is what are the constraints on T and U when calling forward.

Use Cases

Six use cases will be presented:

  1. Should forward an lvalue as an lvalue. All implementations pass this test. But this is not the classic perfect forwarding pattern. The purpose of this test is to show that implementation 2 fails in its stated goal of preventing all use cases except perfect forwarding.
  2. Should forward an rvalue as an rvalue. Like use case A, this is an identity transformation and this presents a motivating example where the identity transformation is needed.
  3. Should not forward an rvalue as an lvalue. This use case demonstrates a dangerous situation of accidentally creating a dangling reference.
  4. Should forward less cv-qualified expressions to more cv-qualified expressions. A motivating use case involving the addition of const during the forward.
  5. Should forward expressions of derived type to an accessible, unambiguous base type. A motivating use case involving forwarding a derived type to a base type.
  6. Should not forward arbitrary type conversions. This use case demonstrates how arbitrary conversions within a forward lead to dangling reference run time errors.

Each use case was chosen to help direct a specific design decision with regards to std::forward. Thus the design is driven by both engineering judgement and empirical evidence and testing. These use cases also represent reasonable code our customers may write using std::forward. All of the use cases are quite similar to each other in the hopes of easing the presentation.

A. Should forward an lvalue as an lvalue

All of the use cases center around a container C<T> which is somewhat like std::pair and std::tuple in that the container can contain either object types T (such as int) or reference types T (such as const int&).

The container has a converting constructor that takes an rvalue container of some other type U, and extracts the T from the U via a member getter (named get()). Because the container U is known to be an rvalue, it is recast to an rvalue (using std::move()) prior to calling the member get() in the hopes of choosing the right overload as this container may make use of rvalue-this qualifiers. forward is used instead of move in this constructor so as to correctly handle the case when T is an lvalue reference type.

#include <iostream>
#include <list>

template <class T>
struct C
{
    T t_;

    template <class U,
              class = typename std::enable_if
                    <
                        !std::is_lvalue_reference<U>::value
                    >::type>
        C(U&& u) : t_(std::forward<T>(std::move(u).get())) {}
};

class A
{
    int data_;
public:
    explicit A(int data = 1)
        : data_(data) {}
    ~A() {data_ = -1;}

    void test() const
    {
        if (data_ < 0)
            std::cout << "A is destructed\n";
        else
            std::cout << "A = " << data_ << '\n';
    }
};

class Awrap
{
    const A& a_;
public:
    explicit Awrap(const A& a) : a_(a) {}
    const A& get() const {return a_;}
};

template <class C>
void test(C c)
{
    c.t_.test();
}

int main()
{
    std::list<C<const A&> > list;
    A a(3);
    C<const A&> c((Awrap(a)));
    list.push_back(c);
    test(c);
    test(list.front());
}

In this use case the value type of C is const A&. The secondary container is named Awrap and is reminiscent of std::ref (Awrap is not absurd or exceedingly rare code). It stores a const A& and its getter returns a const A&. The forward function in C's constructor forwards a const A& as a const A&.

All 6 implementations compile and output:

A = 3
A = 3

which is the expected, safe and useful answer for this use case. However this is not the prototypical "perfect forwarding" example shown at the beginning of this paper. The fact that implementation 2 allows this use case is contrary to the rationale given for that implementation.

B. Should forward an rvalue as an rvalue

This use case is nearly identical to use case A. Instead of C's value type of const A&, now it is simply A. And instead of the secondary container's getter returning A const& it just returns A. The code for the container C's constructor is identical. This is no accident as the design of the container C is intended to cover both use cases:

#include <iostream>
#include <list>

template <class T>
struct C
{
    T t_;

    template <class U,
              class = typename std::enable_if
                    <
                        !std::is_lvalue_reference<U>::value
                    >::type>
        C(U&& u) : t_(std::forward<T>(std::move(u).get())) {}
};

class A
{
    int data_;
public:
    explicit A(int data = 1)
        : data_(data) {}
    ~A() {data_ = -1;}

    void test() const
    {
        if (data_ < 0)
            std::cout << "A is destructed\n";
        else
            std::cout << "A = " << data_ << '\n';
    }
};

class Awrap
{
    A a_;
public:
    explicit Awrap(const A& a) : a_(a) {}
    A get() const {return a_;}
};

template <class C>
void test(C c)
{
    c.t_.test();
}

int main()
{
    std::list<C<A> > list;
    A a(3);
    C<A> c((Awrap(a)));
    list.push_back(c);
    test(c);
    test(list.front());
}

Implementations 0, 3, 4 and 5 compile this use case and output the safe and expected answer:

A = 3
A = 3

Implementations 1 and 2 fail to compile the example because Awrap::get() returns an rvalue, and in these implementations one can not forward an rvalue as an rvalue. That is:

static_cast<A&&>(A());

is disallowed. If it is not ok for forward to allow use case B, why is it ok for it to allow use case A?

Indeed, I can see no reason at all that this is not a safe, valid and motivating use case.

C. Should not forward an rvalue as an lvalue

To demonstrate this next use case, I start with exactly use case A (the one that everyone passes) and introduce a programming mistake that could easily happen by accident: the removal a single ampersand from the return type of Awrap::get(), accidentally changing the return type from a const lvalue to a const rvalue (though I've seen code in the wild which purposefully returns const rvalues).

#include <iostream>
#include <list>

template <class T>
struct C
{
    T t_;

    template <class U,
              class = typename std::enable_if
                    <
                        !std::is_lvalue_reference<U>::value
                    >::type>
        C(U&& u) : t_(std::forward<T>(std::move(u).get())) {}
};

class A
{
    int data_;
public:
    explicit A(int data = 1)
        : data_(data) {}
    ~A() {data_ = -1;}

    void test() const
    {
        if (data_ < 0)
            std::cout << "A is destructed\n";
        else
            std::cout << "A = " << data_ << '\n';
    }
};

class Awrap
{
    const A& a_;
public:
    explicit Awrap(const A& a) : a_(a) {}
    const A& get() const {return a_;}
};

template <class C>
void test(C c)
{
    c.t_.test();
}

int main()
{
    std::list<C<const A&> > list;
    A a(3);
    C<const A&> c((Awrap(a)));
    list.push_back(c);
    test(c);
    test(list.front());
}

Implementations 0 and 1 compile this code and output:

A = 2097536A
A = 10

which represent garbage answers (a run time error). What has happened is that the lvalue const A& in C has been bound to an rvalue which quickly goes out of scope and gets destructed, resulting in a dangling reference when C.t_ is later referenced.

Note that implementation 0 purposefully, and wrongly, allows forwarding an rvalue to an lvalue. Also note that implementation 1 attempts to prevent forwarding any rvalues at all, but does indeed forward an rvalue as an lvalue in this use case. Implementation 1 fails to prevent what it is trying to prevent.

Implementations 2, 3, 4 and 5 all (rightly) fail to compile use case C, and thus catch this run time error at compile time.

So far we have explored two useful identity use cases and one use case which makes a dangerous change from rvalue to lvalue. Implementations 1 and 2 failed to compile one of the useful identity cases, and implementations 0 and 1 gave run time errors in the dangerous use case. Only implementations 3, 4 and 5 had good behavior in these first 3 use cases.

The next 3 use cases will concentrate on the impact of changing types during the forward.

D. Should forward less cv-qualified expressions to more cv-qualified expressions

This use case explores the impact of adding cv-qualifications under the forward. Subtracting cv-qualifications is not considered by any of these implementations as static_cast disallows such transformations (const_cast would be needed for that).

#include <iostream>
#include <list>

template <class T>
struct C
{
    T t_;

    template <class U,
              class = typename std::enable_if
                    <
                        !std::is_lvalue_reference<U>::value
                    >::type>
        C(U&& u) : t_(std::forward<T>(std::move(u).get())) {}
};

class A
{
    int data_;
public:
    explicit A(int data = 1)
        : data_(data) {}
    ~A() {data_ = -1;}

    void test() const
    {
        if (data_ < 0)
            std::cout << "A is destructed\n";
        else
            std::cout << "A = " << data_ << '\n';
    }
};

class Awrap
{
    A& a_;
public:
    explicit Awrap(A& a) : a_(a) {}
    const A& get() const {return a_;}
          A& get()       {return a_;}
};

template <class C>
void test(C c)
{
    c.t_.test();
}

int main()
{
    std::list<C<const A&> > list;
    A a(3);
    C<const A&> c((Awrap(a)));
    list.push_back(c);
    test(c);
    test(list.front());
}

The only difference between use case A, and use case D, is that in D Awrap's getters are now overloaded on const, much like all of the standard containers are overloaded on const for front() and back(). Also Awrap is now holding an A& instead of a const A&.

In this use case forward is being asked to cast an A& to a const A&. This should be a safe (and often needed) situation. Please note that for all of these use cases (A, B, C and D), the implementation of C<T> has not changed. C<T> is designed to handle all of these cases properly.

Implementations 0, 1, 3, 4, and 5 compile this code and output:

A = 3
A = 3

Implementation 2 fails to compile as it constrains the static_cast to not add const. Also note that the supporters of implementation 2 will argue that forward shouldn't be used in this context anyway; just use static_cast<T&&>. But note that this very advice leads our customer into an otherwise preventable run time error, as demonstrated by use case C.

So far, only implementations 3, 4 and 5 have passed (or correctly refused to compile) all use cases. The next two use cases will differentiate these three implementations from each other.

E. Should forward expressions of derived type to an accessible, unambiguous base type

In this use case an extra constructor is added to C<T>. The purpose of this constructor is to serve as the move constructor of the container. Additionally, a new client writes Derived<T> which derives from C<T>. In this use case we explore how to write the move constructor for Derived<T>.

#include <utility>

template <class T>
struct C
{
    T t_;

    C() {}

    template <class U,
              class = typename std::enable_if
                    <
                        !std::is_lvalue_reference<U>::value
                    >::type>
        C(U&& u) : t_(std::forward<T>(std::move(u).get())) {}

    C(C&& c) : t_(std::forward<T>(c.t_)) {}
};

template <class T>
struct Derived
    : C<T>
{
    Derived() {}
    Derived(Derived&& d) : C<T>(std::move(d)) {}
};

int main()
{
    Derived<int> d;
    Derived<int> d2(std::move(d));
}

The above is our first attempt at the move constructor for Derived<T>. It simply casts d to an rvalue using move and then passes it to the base class. Unfortunately this does not compile:

error: 'struct Derived<int>' has no member named 'get'

Unfortunately C<T>'s generic constructor is being called instead of the intended move constructor for C<T>. A smart thing to do would be to further constrain C<T>'s generic constructor to not accept things that aren't a C or that don't have C as a base class. However, let's say that the author of Derived does not have write access to C's source code (a common case in the real world).

Now the author of Derived must deal with this within his own move constructor. There are three possibilities:

  1. Derived(Derived&& d) : C<T>(std::move(static_cast<C<T>&>(d))) {}
    
  2. Derived(Derived&& d) : C<T>(static_cast<C<T>&&>(d)) {}
    
  3. Derived(Derived&& d) : C<T>(std::forward<C<T>>(d)) {}
    

In my opinion, #3 wins hands down in the clarity/readability department. It says that you are going to forward the lvalue d of type Derived<T> as an rvalue of type C<T>. At the very least, #3 is a very reasonable choice for our customers to make.

Implementations 0, 1, 3 and 5 allow our customer to write his constructor as shown above using forward. Implementations 2 and 4 disallow it. I see no rationale to disallow it. The resulting code is safe, clear and useful.

A subtle but critically important fact to note is that in the expression:

static_cast<C<T>&&>(d);

which is found inside of the forward: no temporary is generated as a result of the static_cast. This brings us to our last use case which does generate a temporary during the static_cast.

F. Should not forward arbitrary type conversions

In this last use case a "converting move constructor" is written for our container C<T>. This is much like the converting move constructor you might find in standard classes such as tuple and unique_ptr.

#include <iostream>
#include <utility>
#include <type_traits>

template <class T>
struct C
{
    T t_;

    C() {}

    explicit C(const T& t) : t_(t) {}

    template <class U,
              class = typename std::enable_if
                    <
                        std::is_convertible<U, T>::value
                    >::type>
        C(C<U>&& c) : t_(std::forward<T>(c.t_)) {}
};

class B;

class A
{
    int data_;

    friend class B;
public:
    explicit A(int data = 1)
        : data_(data) {}
    ~A() {data_ = -1;}

    void test() const
    {
        if (data_ < 0)
            std::cout << "A is destructed\n";
        else
            std::cout << "A = " << data_ << '\n';
    }
};

class B
{
    int data_;
public:
    explicit B(int data = 1)
        : data_(data) {}
    B(const A& a) : data_(a.data_) {}
    B(A&& a) : data_(a.data_) {a.data_ = 100;}
    ~B() {data_ = -1;}

    void test() const
    {
        if (data_ < 0)
            std::cout << "B is destructed\n";
        else
            std::cout << "B = " << data_ << '\n';
    }
};

int main()
{
    A a(3);
    C<A> ca(a);
    C<const B&> cb(std::move(ca));
    cb.t_.test();
}

To exercise this use case, I've created two client classes A and B, and A is implicitly convertible to B, though not via a derived-to-base conversion.

C<T>'s constructor accepts an rvalue C<U> where U must be implicitly convertible to T. The constructor uses forward, not move so that lvalue reference types can be held by C (though not exercised in this example).

Implementations 0, 1 and 3 compile this code (albeit with a warning) and output:

B is destructed

or

B = 0

Which both indicate a dangling reference problem. The reason for this is that the static_cast<B&&>(c.t_) is creating a temporary which prematurely destructs.

Implementations 2, 4 and 5 correctly fail to compile this dangerous code.

Summary

Six implementations of forward have been tested against 6 use cases. Four of these use cases present safe and useful code. Two of them illustrate dangling references. In the table below, the matrix of implementation vs use case is compiled. "pass" means that the implementation compiled and got the right answer for those use cases which were safe. For those use cases which created dangling references, the implementation gets a "pass" if it fails to compile. Else an "rfail" is noted which stands for "run time failure". For those use cases representing useful code, if the implementation failed to compile it, this is noted with "cfail" (compile time failure) in the table below.

  1. Should forward an lvalue as an lvalue.
  2. Should forward an rvalue as an rvalue.
  3. Should not forward an rvalue as an lvalue.
  4. Should forward less cv-qualified expressions to more cv-qualified expressions.
  5. Should forward expressions of derived type to an accessible, unambiguous base type.
  6. Should not forward arbitrary type conversions.
forward implementation results for each use case
      ABCDEFscore
0 pass  pass  rfail  pass  pass  rfail 67%
1 pass  cfail  rfail  pass  pass  rfail 50%
2 pass  cfail  pass  cfail  cfail  pass 50%
3 pass  pass  pass  pass  pass  rfail 83%
4 pass  pass  pass  pass  cfail  pass 83%
5 pass  pass  pass  pass  pass  pass 100%

Only implementation 5 passes all six tests, and this is what this paper strongly recommends. It provides readability, flexibility, and safety over the use of static_cast<T&&>. Implementations 0, 1 and 3 (and 0 is a synonym for static_cast<T&&>), provide an opportunity for run time errors which the other implementations catch at compile time. Implementations 2 and 4 fail to compile safe and useful use cases.

Teachability

How do we describe to our customers what (implementation 5) std::forward does? I am not known for great text books educating people on C++. However below is my attempt to teach the mechanics of move and forward.

std::move(t) will cast the expression t to an rvalue. One performs this cast to indicate that you no longer care about the value of t after the expression it is used in. For example:

y = std::move(x); // y now has the value of x, and we no longer care about the value of x
x = new_value();  // because we are about to assign it a new value anyway.

std::forward<T>(u) is similar to move(u) in that it can be used to cast to an rvalue. However forward has two inputs: u and T. The input T is responsible for telling forward whether to cast u to an lvalue, or to an rvalue. If T is an lvalue reference type, then u is cast to an lvalue of that type, else u is cast to an rvalue of that type. For example:

y = std::forward<A&>(x);  // x is copied to y
z = std::forward<A>(x);   // x is moved  to z

One typically needs to use forward when writing generic code and you do not know whether the type parameter input to forward is going to be an lvalue reference type or not. For example:

template <class T, class A1>
std::shared_ptr<T>
factory(A1&& a1)
{
    // If A1 is an lvalue reference type,
    //   T's constructor sees an lvalue a1 (and will likely copy from a1).
    //   Else T's constructor sees an rvalue a1 (and will likely move from a1).
    return std::shared_ptr<T>(new T(std::forward<A1>(a1)));
}

See chapter X for details on the template argument deduction rules which explain when A1 can be deduced as an lvalue reference type.

Since there are two inputs to forward (u and T) it is possible that u is not the same type as T. forward restricts the type transformations from u to T to those which do not create temporaries and/or lead to dangling reference situations, making it safer than using static_cast<T&&>(u). forward is also restricted to those transformations which do not cast away cv-qualifiers.

Given:

template <class T, class U>
T&&
forward(U&& u);

the precise constraints are:

Don't worry about getting these constraints wrong. If you make a mistake, it will be caught at compile time.

Proposed Wording

Change [forward]:

template <class T> T&& forward(typename identity<T>::type&& t);
template <class T, class U>
  T&&
  forward(U&& u);

[Note: The use of identity forces users to explicitly specify the template parameter. This is necessary to get the correct forwarding semantics. — end note]

Returns: t static_cast<T&&>(u).

Remarks: If the following constraints are not met, this signature shall not participate in overload resolution:

[Example: ...

Acknowledgements

Thanks to Daniel Krügler for a generously provided review.