patterncppMinor
C++ identity function
Viewed 0 times
identityfunctionstackoverflow
Problem
I've implemented an
I make it a
The code is written in C++14.
Usage:
Since
EDIT: I changed "a variable
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::const_iterator>>).The code is written in C++14.
#include
static constexpr struct identity_t {
template
constexpr decltype(auto) operator()(T&& t) const noexcept
{
return std::forward(t);
}
} identity{};Usage:
int x = 42;
identity(x) = 24;
constexpr int size = 123;
int arr[identity(size)] = {};
vector v1 = {1, 2, 3, 4}, v2;
v2.reserve(v1.size());
transform(v1.cbegin(), v1.cend(),
back_inserter(v2),
identity);
template
class my_container {
public:
template
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.Solution
Well, that looks like a straightforward use of perfect forwarding in the parameter (
There are plenty of places in C++ where
Replacing
As mentioned in the comments on the question, the name of a function template can deduce what kind of function pointer to decay to:
The name of a local variable gets copy elision (a.k.a. Named Return Value Optimization, or NRVO):
The name of a local variable is treated as an rvalue in the context of
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
Braced initializers and string literals look like expressions, but in the context of an initializer, they're special cases:
Now, I can't imagine how you'd use
T&&, std::forward) 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 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.Code Snippets
template<class T> void f(T) {}
void (*fp)(int) = identity(f);struct S {
S() {}
S(const S&) { puts("copy"); }
S(S&&) { puts("move"); }
};
S f() {
S s;
return identity(s);
}
int main() { f(); }struct S {
S() {}
S(const S&) { puts("copy"); }
S(S&&) { puts("move"); }
};
std::any f() {
S s;
return identity(s);
}
int main() { f(); }struct S {
S() { puts("ctor"); }
S(const S&) = delete;
S(S&&) = delete;
~S() { puts("dtor"); }
};
int main() {
const S& x = identity(S());
puts("done");
}const char x[] = identity("hello");
int y = identity({5});Context
StackExchange Code Review Q#134627, answer score: 7
Revisions (0)
No revisions yet.