Title
forward broken
Status
resolved
Section
[forward]
Submitter
Howard Hinnant

Created on 2009-03-13.00:00:00 last changed 171 months ago

Messages

Date: 2010-11-20.00:05:46

[ 2009-10 Santa Cruz: ]

NAD EditorialResolved. Solved by N2951.

Date: 2009-09-27.00:00:00

[ 2009-09-27 Howard adds: ]

A paper, N2951, is available which compares several implementations (including David's) with respect to several use cases (including Jason's) and provides wording for one implementation.

Date: 2009-08-02.00:00:00

[ 2009-08-02 David adds: ]

forward was originally designed to do one thing: perfect forwarding. That is, inside a function template whose actual argument can be a const or non-const lvalue or rvalue, restore the original "rvalue-ness" of the actual argument:

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) );
}

Attempting to engineer forward to accomodate uses other than perfect forwarding dilutes its idiomatic meaning. The solution proposed here declares that forward<T>(x) means nothing more than static_cast<T&&>(x), with a patchwork of restrictions on what T and x can be that can't be expressed in simple English.

I would be happy with either of two approaches, whose code I hope (but can't guarantee) I got right.

  1. 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& x)
    {
        return static_cast<T&&>(x);
    }
    
  2. 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<
        // in forward<T>(x), x is a parameter of the caller, thus an lvalue
        is_lvalue_reference<U>::value
        // in caller's deduced T&& argument, T can only be non-ref or lvalue ref
        && !is_rvalue_reference<T>::value
        // Must not cast cv-qualifications or do any type conversions
        && is_same<T&,U&>::value
        , T&&>::type forward(U&& a)
    {
        return static_cast<T&&>(a);
    }
    
Date: 2009-08-02.00:00:00

[ 2009-08-02 Howard adds: ]

My current preferred solution is:

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<T>::value &&
        is_lvalue_reference<U>::value>::type,
   class = typename enable_if<
        is_same<typename __base_type<T>::type,
                typename __base_type<U>::type>::value>::type>
inline
T&&
forward(U&& t)
{
   return static_cast<T&&>(t);
}

This has been tested by Bill, Jason and myself.

It allows the following lvalue/rvalue casts:

  1. Cast an lvalue t to an lvalue T (identity).
  2. Cast an lvalue t to an rvalue T.
  3. Cast an rvalue t to an rvalue T (identity).

It disallows:

  1. Cast an rvalue t to an lvalue T.
  2. Cast one type t to another type T (such as int to double).

"a." is disallowed as it can easily lead to dangling references. "b." is disallowed as this function is meant to only change the lvalue/rvalue characteristic of an expression.

Jason has expressed concern that "b." is not dangerous and is useful in contexts where you want to "forward" a derived type as a base type. I find this use case neither dangerous, nor compelling. I.e. I could live with or without the "b." constraint. Without it, forward would look like:

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

Or possibly:

template <class T, class U,
   class = typename enable_if<
       !is_lvalue_reference<T>::value ||
        is_lvalue_reference<T>::value &&
        is_lvalue_reference<U>::value>::type,
   class = typename enable_if<
        is_base_of<typename __base_type<U>::type,
                   typename __base_type<T>::type>::value>::type>
inline
T&&
forward(U&& t)
{
   return static_cast<T&&>(t);
}

The "promised paper" is not in the post-Frankfurt mailing only because I'm waiting for the non-concepts draft. But I'm hoping that by adding this information here I can keep people up to date.

Date: 2010-10-21.18:28:33

[ Batavia (2009-05): ]

Move to Open, awaiting the promised paper.

Date: 2009-03-13.00:00:00

This is a placeholder issue to track the fact that we (well I) put the standard into an inconsistent state by requesting that we accept N2844 except for the proposed changes to [forward].

There will exist in the post meeting mailing N2835 which in its current state reflects the state of affairs prior to the Summit meeting. I hope to update it in time for the post Summit mailing, but as I write this issue I have not done so yet.

History
Date User Action Args
2010-11-19 19:04:45adminsetstatus: nad editorial -> resolved
2010-10-21 18:28:33adminsetmessages: + msg513
2010-10-21 18:28:33adminsetmessages: + msg512
2010-10-21 18:28:33adminsetmessages: + msg511
2010-10-21 18:28:33adminsetmessages: + msg510
2010-10-21 18:28:33adminsetmessages: + msg509
2009-03-13 00:00:00admincreate