Document number: N2835=09-0025

Howard E. Hinnant
2009-03-22

forward

Contents

Introduction


Update: This paper was written prior to the Summit meeting, but missed the pre-meeting mailing deadline. In Summit we accepted a minor revision of N2831 "Fixing a Safety Problem with Rvalue References: Proposed Wording", except for the clause refering to forward. I did not have time to update this paper any more than this prior to the post Summit mailing. I still believe that the fundamental point of this paper is sound:

It is both safe and useful to forward rvalues as rvalues (rows 7 and 8).

In Summit we discussed constraining forward to disallow all conversions except for adding cv-qualifiers, and except for the change in lvalue/rvalue-ness. This is feasible to do, can be applied to any of the solutions discussed herein, and is probably a good idea. E.g.:

double get_double();
int    get_int();
forward<int>(get_double());  // forward an rvalue double as an rvalue int:  Error, should not compile
forward<int>(get_int());     // forward an rvalue int as an rvalue int:     Ok

Paper N2831 "Fixing a Safety Problem with Rvalue References: Proposed Wording" recommends certain language and library changes to prevent accidentally applying move semantics to an lvalue. I am in favor of the proposed language changes and, with a single exception, I am also in favor of the proposed library changes.

That single exception, the specification of forward, is the subject of this paper. I will compare the behaviors of forward as specified in N2831 and N2800, and will contrast those with a proposed third specification whose behavior I believe to be:

In particular, I believe the N2831 forward unnecessarily causes some safe, valid and reasonable code to fail at compile time. After contrasting the three behaviors, I will present an example that demonstrates the impact of the the various specifications.

forward Revealed

To examine the behaviors of the various specifications of forward, eight cases must be considered, shown in the "Call" column of the following table:

Case # Call Description N2800 result N2831 result Proposed result
1
forward<      A&>(lvalue A)
Forward a non-const lvalue as a non-const lvalue
lvalue A
lvalue A
lvalue A
2
forward<const A&>(lvalue A)
Forward an lvalue as a const lvalue
lvalue const A
lvalue const A
lvalue const A
3
forward<      A&>(rvalue A)
Forward a non-const rvalue as a non-const lvalue
rejected
rejected
rejected
4
forward<const A&>(rvalue A)
Forward an rvalue as a const lvalue
lvalue const A
lvalue const A
rejected
5
forward<      A >(lvalue A)
Forward a non-const lvalue as a non-const rvalue
rvalue A
rvalue A
rvalue A
6
forward<const A >(lvalue A)
Forward an lvalue as a const rvalue
rvalue const A
rvalue const A
rvalue const A
7
forward<      A >(rvalue A)
Forward a non-const rvalue as a non-const rvalue
rvalue A
rejected
rvalue A
8
forward<const A >(rvalue A)
Forward an rvalue as a const rvalue
rvalue const A
rvalue const A
rvalue const A

As summarized in the above table, there is already agreement in six of the eight cases. Only cases 4 and 7 are in dispute: should forward reject case 4, case 7, or neither? In the next section, I will present an example to motivate the behavior (shown in the right column above) that I propose in this paper, namely that one ought not forward an rvalue as an lvalue, but that all other forwarding is sensible, including forwarding rvalues as rvalues.

Example

The example code is a sketch of a container (I call it C) which is capable of holding either object types or lvalue references (similar to tuple and pair). I will refer to the container's element type as either A or A& to specify when I'm referring to an object element type or an lvalue reference element type which the container contains. C has a constructor which will take an rvalue argument of some other type of container (U) which holds an A or A&:

U u;
C<A> c(std::move(u));

This paper will concentrate on the implementation of this constructor for its example.

The container U has a (possibly overloaded) member get() for accessing the contained value.

A a = u.get();

It is via this u.get() interface that the container C will gain access to U's element, move constructing in case the element type is A, else copy constructing for element types of A&.

To simplify the discussion, I will neglect any other data members of C, including those that might participate in this constructor. I will also assume (for simplification) that the contained type of C and the contained type of U have the same type (no conversions).

[Footnote: The "same type" restriction above, and mentioned in the concepts below, caused some concern, or at the very least curiosity with some reviewers. Real world code may well relax this restriction and this would typically involve using forward<U::value_type>(u.get()) instead of the forward<T>(u.get()) shown below. Everything still works the same, though it becomes even more important for forward to have "good behavior" because things also get more complicated to reason about. This paper is striving to avoid this complication because though sometimes necessary in real world code (e.g. unique_ptr), the main point of the paper can be demonstrated without this extra complication.]

One degree of flexibility I would like to allow however is that the get() accessor of U may return any of A&, A&&, or A. The author of C does not know nor care about the specific return of U::get(). He simply wants C to work correctly no matter what the client U throws at him.

Here is a conceptized implementation of the constructor:

// T can be A or A&
template <std::VariableType T>
requires !std::RvalueReference<T>
class C
{
    T t_;
public:
    // Move construct t_ from u.get() if T is A,
    //   else copy construct t_ from u.get() when T is A&.
    //   u.get() may return T&, T&& or T (which
    //   all collapse to A& when T is A&).  This
    //   constructor should always just do the right thing regardless.
    template <class U>
      requires !std::LvalueReference<U>
            && std::IsSame<U::value_type, T>
            && ConstructibleFromForwardGet<T, U>
      explicit C(U&& u)
        : t_(std::forward<T>(u.get())) {}
};

My use of concepts in this example should not imply that this is a concepts issue, it is not. This is an issue concerning the functionality of forward, and how N2831 proposes to alter that functionality. I have the same concerns about forward whether it is used with concepts or not. Here is the same constructor written without concepts:

template <class T>
class C
{
    T t_;
public:
    // Move construct t_ from u.get() if T is A,
    //   else copy construct t_ from u.get() when T is A&.
    //   u.get() may return T&, T&& or T.
    template <class U>
      explicit C(U&& u,
          typename std::enable_if
                     <
                       !std::is_lvalue_reference<U>::value
                     >::type* = 0)
        : t_(std::forward<T>(u.get())) {}
};

Disclaimer: Testing for this paper used the non-concepts version.

With the N2800 specification of forward the above constructor works as desired (confirmed with tests) for all cases A and A& with u.get() returning either an lvalue or rvalue (except for one case which N2831 doesn't fix, but is fixed by this proposal).

With the N2831 specification of forward the above constructor fails with the following quite reasonable code:

struct A {};

class U
{
    A a_;  // value_type is A
public:
    A get() {return a_;}
};

int main()
{
    U u;
    C<A> c(std::move(u));
}

test.cpp: In constructor 'C<T>::C(U&&, typename
          std::enable_if<(!
          std::is_lvalue_reference::value), void>::type*) [with
          U = U, T = A]':
test.cpp:40:   instantiated from here
test.cpp:18: error: no matching function for call to 'forward(A)'

In C's constructor, u.get() is returning an rvalue A which the N2831 forward<A> summarily rejects (case #7). This should be an identity operation (forward a non-const rvalue as a non-const rvalue). No other identity operations are rejected by N2831, including forwarding a const rvalue as a const rvalue.

I believe if we adopt the N2831 forward specification, the author of C will simply (and rightly) avoid std::forward:

explicit C(U&& u)
  : t_(std::forward<T>static_cast<T&&>(u.get())) {}

I can see no motivation for pushing the author of C away from the use of forward. Indeed I believe we can make the use of forward superior to the use of static_cast (see below).

Note that the use of move is not appropriate here as it would break the case when T is A&

explicit C(U&& u)              // Wrong!
  : t_(std::move(u.get())) {}  // binds rvalue to a non-const ref when T is A&

Note further that simply copy constructing is not appropriate here as it would break the case when T is A and u.get() returns an lvalue:

explicit C(U&& u)   // Wrong!
  : t_(u.get()) {}  // copy constructs t_ instead of move constructs
                    // when T is A and u.get() returns an lvalue

I alluded above to one problem which both the N2800 and N2831 specifications allow and shouldn't:

class U
{
    const A& a_;  // value_type is const A&
public:
    U(const A& a) : a_(a) {}
    A get() {return a_;}
};

int main()
{
    A a;
    U u(a);
    C<const A&> c(std::move(u));
    // c.t_ dangles here
}

Here the rvalue returned from u.get() is binding to C's internal const A&. After the constructor completes, this internal reference is left dangling. The danger is that we're allowing an rvalue to be forwarded as an lvalue (case #4).

To fix this problem (and the one that N2831 attempts to introduce), we should only outlaw forwarding rvalues as lvalues. With this specification, the author of C has both readability and safety motivations to use std::forward instead of static_cast:

// Works when it is supposed to.
// Doesn't work when it is not supposed to.
explicit C(U&& u)
  : t_(std::forward<T>static_cast<T&&>(u.get())) {}

Proposed Wording

This wording is consistent with the proposed wording of N2831 except for N2831's modifications to forward. It implements forwarding except for rejecting cases #3 and #4 as described in the previous section.

Change the <utility> synopsis in 20.2 [utility]:

// 20.2.2, forward/move: 
template <IdentityOf T> requires !LvalueReference<T> T&& forward(IdentityOf<T>::type&&); 
template <IdentityOf T> requires !LvalueReference<T> T&& forward(RvalueOf<T>::type t);
template <IdentityOf T> requires LvalueReference<T> T forward(IdentityOf<T>::type t);
template <IdentityOf T> requires LvalueReference<T> T forward(RvalueOf<T>::type t) = delete;
template <RvalueOf T> RvalueOf<T>::type move(T&&);

Change 20.2.2 [forward]:

// forward lvalue as rvalue
template <IdentityOf T>
  requires !LvalueReference<T>
  T&&
  forward(IdentityOf<T>::type&& t);
// forward rvalue as rvalue
template <IdentityOf T>
  requires !LvalueReference<T>
  T&&
  forward(RvalueOf<T>::type t);

...

-3- Returns: static_cast<T&&>(t).

// forward lvalue as lvalue
template <IdentityOf T>
  requires LvalueReference<T>
  T
  forward(IdentityOf<T>::type t);

Returns: t.

Note: The forwarding of rvalues as lvalues is intentionally disabled to prevent dangling reference accidents when binding an rvalue to an lvalue reference to const.

-4- [Example: ...

Acknowledgements

The generous help of Daniel Krügler, Peter Dimov and Walter Brown is gratefully acknowledged. This is a much clearer paper thanks to their excellent recommendations.

I would also like to acknowledge the work of David Abrahams and especially Douglas Gregor in the preparation of N2831. I believe N2831 is a significant improvement to our current draft standard N2800, excepting this one debate with forward.