I've implemented an identity
function (well, actually a functor struct
) in C++. The goal is that every occurrence of an expression expr
in the code can be substituted by identity(expr)
without changing the semantics of the program at all.
I make it a struct
with a templated operator()
instead of simply a function template because this enables us to pass identity
as an argument to other functions (say std::transform
) without needing to specify the type (e.g., identity<std::pair<std::vector<int>::const_iterator, std::vector<double>::const_iterator>>
).
The code is written in C++14.
#include <utility>
static constexpr struct identity_t {
template<typename T>
constexpr decltype(auto) operator()(T&& t) const noexcept
{
return std::forward<T>(t);
}
} identity{};
Usage:
int x = 42;
identity(x) = 24;
constexpr int size = 123;
int arr[identity(size)] = {};
vector<int> v1 = {1, 2, 3, 4}, v2;
v2.reserve(v1.size());
transform(v1.cbegin(), v1.cend(),
back_inserter(v2),
identity);
template<typename T>
class my_container {
public:
template<typename F>
auto flat_map(F&& f) const { ... }
auto flatten() const { return flat_map(identity); }
...
};
Since identity(expr)
is nothing more than a cast, performance should not be a problem. I'm concerned whether there are cases in which this implementation fails to keep the program's behavior unchanged. If there are, how can I fix the implementation?
EDIT: I changed "a variable x
" in the first paragraph to "an expression expr
". The latter is more accurate.
1 Answer 1
Well, that looks like a straightforward use of perfect forwarding in the parameter (T&&
, std::forward<T>
) and in the return type (decltype(auto)
). So yep, you've got it.
There are plenty of places in C++ where x
and identity(x)
don't do the same thing, either because x
is a name as well as an expression, or because x
is a construct that looks like an expression but technically is not one. (Personally, I consider almost all of these places to be mistakes in the design of C++.) Here are the ones I can think of.
Replacing identity(x)
with x
in all of the following examples changes the behavior of the code sample.
As mentioned in the comments on the question, the name of a function template can deduce what kind of function pointer to decay to:
template<class T> void f(T) {}
void (*fp)(int) = identity(f);
The name of a local variable gets copy elision (a.k.a. Named Return Value Optimization, or NRVO):
struct S {
S() {}
S(const S&) { puts("copy"); }
S(S&&) { puts("move"); }
};
S f() {
S s;
return identity(s);
}
int main() { f(); }
The name of a local variable is treated as an rvalue in the context of return
:
struct S {
S() {}
S(const S&) { puts("copy"); }
S(S&&) { puts("move"); }
};
std::any f() {
S s;
return identity(s);
}
int main() { f(); }
Lifetime extension applies to temporaries whose construction is "visible" in some sense that is extremely subtle when I start thinking hard about it. Interposing the call to identity
disables lifetime extension.
struct S {
S() { puts("ctor"); }
S(const S&) = delete;
S(S&&) = delete;
~S() { puts("dtor"); }
};
int main() {
const S& x = identity(S());
puts("done");
}
Braced initializers and string literals look like expressions, but in the context of an initializer, they're special cases:
const char x[] = identity("hello");
int y = identity({5});
Now, I can't imagine how you'd use identity
in your codebase where any of these quirks might matter; but then, I don't quite see how you plan to use identity
at all. Your only fleshed-out example is just using std::transform(..., identity)
as a verbose way of writing std::copy(...)
, which doesn't seem very useful.
Explore related questions
See similar questions with these tags.
...
's in your code suggest that there is more to your implementation, which you need to include. Right now this question is off topic as it is basically stub code. \$\endgroup\$...
)? \$\endgroup\$identity
itself does work. \$\endgroup\$identity
. However that's a limitation of the language that cannot be avoided. \$\endgroup\$