ISO/IEC JTC1 SC22 WG21 N????

Date:

Jeffrey Yasskin <jyasskin@google.com>

The identity type transformation

Skip table of contents

Overview

The SGI STL provided an identity functor with an operator() that returned its argument, and this has been imitated in at least libstdc++.

template<class T>
class identity : public unary_function<T, T> {
  T& operator()(T& arg) const { return arg; }  // libstdc++ only?
  const T& operator()(const T& arg) const { return arg; }
};

More recently, a need has arisen for an identity type transformation to select which of several function arguments should contribute to template argument deduction. (A no-longer-used definition of std::forward(), comparators in string_view, and in a suggestion for optional<> in c++std-lib-34391)

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

Some working drafts of C++11 defined an identity template that merged the two definitions (suggested by issue 700), but after issues 823 and 939 the whole class was removed.

The need for the identity type transformation and backward-compatibility with the SGI library are still around, so this paper proposes two (mutually exclusive) options for nearly achieving both.

Just use decay or remove_reference

Every potential use of identity<T>::type I'm familiar with could also be written as decay<T>::type or remove_reference<T>::type without significantly changing the meaning. So why introduce an identity type transformation? Let's look at two uses, with identity replaced by remove_reference:

In c++std-lib-34400, Howard Hinnant suggested:

operator==(const optional<T>&, const typename remove_reference<T>::type&)

in order to cause template argument deduction to proceed based on only the first argument to this operator==. This prompted the reply, "Do we need remove_reference? std::optional<U&> is not allowed." in c++std-lib-34402. I expect that sort of question to come up often as we use this technique more without a dedicated identity transformation. Experts will be able to figure out what's going on if they're paying attention, but users will assume that the transformation can actually change its argument.

In N3685, string_view, I suggest an implementation of comparison including:

template<typename charT, typename traits>
  bool operator==(basic_string_view<charT, traits> lhs,
                  typename __identity<basic_string_view<charT, traits>>::type rhs);

My goal was the same here, to cause template argument deduction to proceed based on only one argument. Replacing __identity with remove_reference would be confusing, since its argument is clearly not a reference. Is basic_string_view an alias template which could result in a reference? No, we're just using a term we don't mean because it's all the standard gives us.

The standard ought to give users the terminology they actually want.

Use a different name

Daniel Krugler suggested naming the type transformation "identity_of" to avoid conflicting with the SGI library and to match the IdentityOf concept in one of the C++11 working drafts. This is effectively the same definition as in the working paper in 2008 before issue 700 was applied, but it lives in <type_traits> instead of <utility>.

Wording

In "Header <type_traits> synopsis [meta.type.synop]", add to "other transformations":

template <class T> struct identity_of;
template <class T>
  using identity_of_t = typename identity_of<T>::type;

Add to the table in "Other transformations [meta.trans.other]":

TemplateConditionComments
template <class T> struct identity_of; The member typedef type shall name T.

Infelicities

Arguably, identity_of_t<T> is not the teminology users actually want either. For the uses I've seen, we actually want to exclude a function argument from template argument deduction. So, omit_from_deduction<T> might be an even more useful alias template.

If we choose this option and someday decide to standardize the SGI identity function object type as well, we're likely to confuse users, who will wonder "What's the difference? Which should I use? Why do we not just have one of these?", as Howard Hinnant put it in an earlier draft of this paper.

Fix the issues

Another option is to try to produce an identity template that makes everyone happy. This implies that identity<T>::type == T, an instance of identity<T> must be the identity function on values of type T, we have to fix issues 823 and 939, and if we omit parts of the SGI definition, it must be possible to write a conforming extension that accepts the same programs as the SGI library. I think this is nearly possible, although some code will be able to tell the difference.

If library vendors think this specification will be a problem, I would appreciate hearing your concerns.

We have some choices for how to specify operator() in a way that fixes issue 939:

Wording

To "Header <functional> synopsis" in [function.objects], add:

// [function.objects.identity], identity:
template <class T=void> struct identity;
template <> struct identity<void>;

Add a section [function.objects.identity] inside [function.objects]:

template<class T = void> struct identity {
  template<class U> U&& operator()(U&&) const;
  typedef T argument_type;
  typedef T result_type;
  // [Note: The following typedef makes identity<T> a Transformation Trait ([meta.rqmts])
  // in addition to a function object type. -- end Note]
  typedef T type;
};
template<class U> U&& operator()(U&& arg) const;

Returns: std::forward<U>(arg).

Remarks: This function shall not participate in overload resolution unless <pick a choice of calls to accept>.

Notes: When arg is passed an lvalue, U will be an lvalue reference type, avoiding a copy or move.

template<> struct identity<void> {
  template<class U> U&& operator()(U&&) const;
  typedef unspecified is_transparent;
  typedef void type;
};
template<class U> U&& operator()(U&& arg) const;

Returns: std::forward<U>(arg).

Rationale

Making identity::operator() a template solves issue 823 by allowing the template to be instantiated without operator() being instantiable.

This proposal doesn't say that identity<T> derives from unary_function<T, T>. I believe [derivation] says that an implementation can add such derivation as a conforming extension, and [diff.cpp03.utilities] explicitly calls out this removal as an incompatibility in other function objects, so it shouldn't be a problem for this object either.

It's not essential to provide the transparent version of identity<>, but it's consistent with the other function object types. We might prefer to make identity a non-templated class that only provides this generic operator(), but that would both be incompatible with the SGI class and would require identity_of for the transformation trait.

Infelicities

Rvalue arguments produce xvalue results in this proposal, while they produce lvalue results in the SGI and libstdc++ implementations. Non-const lvalue arguments produce non-const lvalue results in this proposal and libstdc++, while the SGI documentation says they produce const lvalue results there.

For identity<T*>::operator(), 0 and nullptr cannot be arguments in this proposal, because they don't deduce to the right type. The SGI interface would produce a safe result in that case.

Acknowledgements

I'd like to thank Daniel Krügler and Howard Hinnant for reviewing a draft of this paper and suggesting several improvements.