939. Problem with std::identity and reference-to-temporaries

Section: 23.2.4 [forward] Status: C++11 Submitter: Alisdair Meredith Opened: 2008-12-11 Last modified: 2016-02-10

Priority: Not Prioritized

View all other issues in [forward].

View all issues with C++11 status.

Discussion:

std::identity takes an argument of type T const & and returns a result of T const &.

Unfortunately, this signature will accept a value of type other than T that is convertible-to-T, and then return a reference to the dead temporary. The constraint in the concepts version simply protects against returning reference-to-void.

Solutions:

i/ Return-by-value, potentially slicing bases and rejecting non-copyable types

ii/ Provide an additional overload:

template< typename T >
template operator( U & ) = delete;

This seems closer on intent, but moves beyond the original motivation for the operator, which is compatibility with existing (non-standard) implementations.

iii/ Remove the operator() overload. This restores the original definition of the identity, although now effectively a type_trait rather than part of the perfect forwarding protocol.

iv/ Remove std::identity completely; its original reason to exist is replaced with the IdentityOf concept.

My own preference is somewhere between (ii) and (iii) - although I stumbled over the issue with a specific application hoping for resolution (i)!

[ Batavia (2009-05): ]

We dislike options i and iii, and option ii seems like overkill. If we remove it (option iv), implementers can still provide it under a different name.

Move to Open pending wording (from Alisdair) for option iv.

[ 2009-05-23 Alisdair provided wording for option iv. ]

[ 2009-07-20 Alisdair adds: ]

I'm not sure why this issue was not discussed at Frankfurt (or I missed the discussion) but the rationale is now fundamentally flawed. With the removal of concepts, std::identity again becomes an important library type so we cannot simply remove it.

At that point, we need to pick one of the other suggested resolutions, but have no guidance at the moment.

[ 2009-07-20 Howard adds: ]

I believe the rationale for not addressing this issue in Frankfurt was that it did not address a national body comment.

I also believe that removal of identity is still a practical option as my latest reformulation of forward, which is due to comments suggested at Summit, no longer uses identity. :-)

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 remove_all<T>::type,
                        typename remove_all<U>::type>::value
            >::type>
inline
T&&
forward(U&& t)
{
    return static_cast<T&&>(t);

}

[ The above code assumes acceptance of 1120 for the definition of remove_all. This is just to make the syntax a little more palatable. Without this trait the above is still very implementable. ]

Paper with rationale is on the way ... really, I promise this time! ;-)

[ 2009-07-30 Daniel adds: See 823 for an alternative resolution. ]

[ 2009-10 Santa Cruz: ]

Move to Ready. Howard will update proposed wording to reflect current draft.

Proposed resolution:

Strike from 23.2 [utility]:

template <class T> struct identity;

Remove from 23.2.4 [forward]:

template <class T> struct identity {
  typedef T type;

  const T& operator()(const T& x) const;
};

const T& operator()(const T& x) const;

-2- Returns: x