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 typedef type, 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.