How std::remove_cvref Works in C++
Introduced in C++20, std::remove_cvref is a type trait
designed to strip top-level const, volatile,
and reference qualifiers from any given type. This article examines the
internal mechanics of std::remove_cvref, why the order of
qualification removal is critical, how it behaves when paired with
template type deduction, and how it differs from older alternatives like
std::decay.
The Core Implementation and Mechanics
The behavior of std::remove_cvref is formally defined as
the composition of two separate type transformations:
std::remove_reference followed by
std::remove_cv.
A standard implementation looks like this:
template<class T>
struct remove_cvref {
using type = std::remove_cv_t<std::remove_reference_t<T>>;
};
template<class T>
using remove_cvref_t = typename remove_cvref<T>::type;When provided with a type T, the trait evaluates in two
strict stages:
- Reference Removal: If
Tis an lvalue reference (U&) or an rvalue reference (U&&), it transformsTinto the underlying referenced typeU. IfTis not a reference, it remains unchanged. - cv-Qualifier Removal: Once the reference is
removed,
std::remove_cvevaluates the resulting type. If it contains top-levelconst,volatile, or both (const volatile), those qualifiers are stripped away to yield the bare type.
Why Order of Operations Matters
The sequence of operations—removing references before cv-qualifiers—is essential due to how C++ models references. In C++, references themselves cannot be cv-qualified; only the type they refer to can be.
Consider the type const int&:
- If you apply
std::remove_cvfirst, the compiler checks whether the reference itself isconstorvolatile. Because a reference cannot be cv-qualified,std::remove_cv_t<const int&>evaluates toconst int&—making no change. - By applying
std::remove_referencefirst,const int&becomesconst int. - Next,
std::remove_cvprocessesconst int, whereconstis now a top-level qualifier on an object type, successfully transforming it intoint.
Stripping Deduced Types in Templates
std::remove_cvref is most commonly used in generic
programming involving forwarding references (T&&).
When taking arguments via forwarding references, template argument
deduction encodes value categories and cv-qualifiers directly into
T:
template <typename T>
void process(T&& arg) {
using CleanType = std::remove_cvref_t<T>;
// ...
}Depending on the argument passed:
- Non-const lvalue (
int x):Tdeduces toint&.std::remove_cvref_t<int&>strips the lvalue reference, yieldingint. - Const lvalue (
const int x):Tdeduces toconst int&.std::remove_cvref_t<const int&>strips the reference first to yieldconst int, then strips theconst, yieldingint. - Non-const rvalue (
10):Tdeduces toint.std::remove_cvref_t<int>evaluates toint. - Const rvalue (
const int{10}):Tdeduces toconst int.std::remove_cvref_t<const int>evaluates toint.
In every scenario, CleanType consistently resolves to
the fundamental, unqualified object type int, making it
ideal for instantiating local copies or constructing return types.
std::remove_cvref vs.
std::decay
Prior to C++20, developers often used std::decay to
strip references and const qualifiers. However,
std::decay mimics the conversions applied when passing an
argument by value, which includes:
- Stripping reference and cv-qualifiers.
- Converting array types to pointer types (e.g.,
int[5]becomesint*). - Converting function types to function pointers (e.g.,
void()becomesvoid(*)()).
std::remove_cvref specifically avoids array-to-pointer
and function-to-pointer decay. If applied to an array reference such as
const int(&)[5], std::remove_cvref_t
evaluates to int[5], whereas std::decay_t
converts it to const int*. This makes
std::remove_cvref a more precise and less intrusive tool
when only qualification stripping is required.