Purpose of std::identity in C++ Parameter Forwarding
Introduced in C++20 under the <functional> header,
std::identity is a standard function object that returns
its argument unchanged while perfectly preserving its value category. In
parameter forwarding and generic programming, it serves as a
standardized, zero-overhead callable that enables transparent
pass-through of arguments, acting as the canonical default projection in
standard algorithms and facilitating consistent forwarding across
higher-order functions.
The Definition of std::identity
To understand how std::identity works with parameter
forwarding, it helps to examine its conceptual implementation:
struct identity {
template <typename T>
constexpr T&& operator()(T&& t) const noexcept {
return std::forward<T>(t);
}
using is_transparent = /* unspecified */;
};The call operator takes any argument by forwarding reference
(T&&) and returns it using
std::forward<T>(t). This guarantees that:
- Lvalues remain lvalues with their original constness preserved.
- Rvalues remain rvalues, allowing downstream code to move resources efficiently.
- No unnecessary copies or temporary objects are created during the function call.
Primary Purpose in Parameter Forwarding
1. Default Transparent Pass-Through in Generic Callables
When designing generic higher-order functions, algorithms, or factory
templates, functions often accept an optional transformation or
projection callable. Rather than requiring users to write custom lambda
expressions like
[](auto&& x) -> decltype(auto) { return std::forward<decltype(x)>(x); },
std::identity provides a pre-defined, standardized
mechanism that forwards parameters without modification.
#include <functional>
#include <utility>
template <typename T, typename Projection = std::identity>
void process_and_forward(T&& value, Projection proj = {}) {
// proj perfectly forwards the projected value
consume(proj(std::forward<T>(value)));
}If the caller provides no custom transformation,
std::identity ensures that value passes
through directly to consume with its original value
category intact.
2. Standardization in the C++20 Ranges Library
The C++20 Ranges algorithms rely heavily on projections. Every
constrained algorithm that accepts a projection defaults to
std::identity. In this role, parameter forwarding
guarantees that algorithms operating over ranges of objects do not incur
extraneous moves or copies when reading elements directly.
#include <algorithm>
#include <vector>
std::vector<int> numbers = {5, 3, 1, 4, 2};
// std::ranges::sort uses std::identity as the default projection
std::ranges::sort(numbers, std::less<>{}, std::identity{});Because std::identity forwards its input through
std::forward<T>, it supports non-copyable types and
move-only types without altering their semantics.
3. Avoiding Lambda Overhead and Boilerplate
Before C++20, forwarding an argument through a generic wrapper required writing verbose generic lambdas:
[](auto&& arg) -> decltype(auto) {
return std::forward<decltype(arg)>(arg);
}std::identity replaces this boilerplate with a standard
type. Because it is an empty class type with a
constexpr noexcept call operator, compilers can easily
optimize it away entirely, resulting in direct forwarding at
machine-code level.
std::identity vs. std::type_identity
A common point of confusion in C++ generic programming is the
difference between std::identity and
std::type_identity:
std::identity(from<functional>) operates on values. It is an invocable function object designed to forward arguments unchanged at runtime or compile-time execution.std::type_identity(from<type_traits>) operates on types. It does not perform value forwarding; instead, it provides the member typedeftype, which is used to create non-deduced contexts in template argument deduction.
Summary
std::identity provides a standard, reusable function
object for perfect forwarding in callable pipelines. By implementing
operator() with std::forward, it ensures that
expressions retain their exact type, const-qualification, and value
category, making it the ideal no-op building block for C++ projections
and higher-order templates.