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.
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.
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>.
template <class T> struct identity_of;
template <class T>
using identity_of_t = typename identity_of<T>::type;
Template | Condition | Comments |
---|---|---|
template <class T> struct identity_of; | The member typedef type shall name T . |
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.
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:
const T& operator()(const T&) const;
template<class U> const U& operator()(const U&) const = delete;
This is the solution 939 suggests. It definitely forbids
implementations from accepting the same programs as the SGI definition
would, since convertible-to-T arguments land on the explicitly-deleted
function, but it fails to fix issue 823, meaning identity<const
void>
fails to instantiate.
const T& operator()(const T&) const;
T& operator()(T&) const;
template<class U> const U& operator()(const U&) const = delete;
This matches the libstdc++ implementation more closely, but otherwise has the same problems as the previous option.
template<class U> U&& operator()(U&&) const;
With "appropriate" "does not participate in overload resolution unless" wording. [member.functions] may allow an implementation to add another templated overload that captures convertible-to-T arguments, so that their users' calls can continue compiling. However, Daniel Krugler dislikes this interpretation, so it's probably a bad idea. Still, this gives us finer control over what arguments are accepted, so it may still be a good option. What should the enable_if condition be?
"decay<U>::type
names the same type as (abbreviated
"==" from here on) decay<T>::type
": This allows
identity<int[3]>{}(&an_int)
to compile, which probably
isn't ideal.
"decay<U>::type
== T
": This forbids
identity<const int>{}(an_int)
.
"decay<U>::type
==
remove_cv<T>::type
": This forbids
identity<int&>{}(3)
, which might be good, but which the
SGI definition allows, but it also allows identity<volatile
int>{}(an_int)
and identity<int>{}(a_volatile_int)
,
which we might or might not want.
// [function.objects.identity], identity:
template <class T=void> struct identity;
template <> struct identity<void>;
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)
.
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.
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.
I'd like to thank Daniel Krügler and Howard Hinnant for reviewing a draft of this paper and suggesting several improvements.