---
title: "Type Aliases in Requires-Expressions"
document: P4281R0
date: 2026-06-20
audience: Evolution Working Group (EWG)
reply-to:
  - "Hewill Kang <hewillk@gmail.com>"
---

- Abstract
  - Revision history
  - Discussion
  - Design
  - Proposed change

## Abstract

This paper proposes extending the syntax of `requires`-expressions to allow `using`-type aliases within the requirement body.

Currently, complex associated types must be spelled out repeatedly within the same constraint block. This causes code duplication and makes concepts hard to read. Allowing local type aliases inside `requires`-expressions eliminates redundant code without polluting the enclosing namespace.

## Revision history

### R0

Initial revision.

## Discussion

The fundamental purpose of a `typename` requirement in a `requires`-expression is to validate that a dependent type is well-formed:

```
template<class T>
concept some-concept = 
  requires { typename some-type<T>; };
```

However, type validation rarely happens in a vacuum. In practice, once a type is proven to exist, developers almost always need to enforce further semantic constraints or concept checks on that same type:

```
template<class T>
concept some-concept = 
  requires { typename some-type<T>; } 
  &&    other-concept<some-type<T>>
  &&  another-concept<some-type<T>>;
```

When dependent type `*some-type<T>*` is a complex or deeply nested trait instantiation, spelling it out repeatedly across the constraint block creates severe text redundancy and increases the likelihood of typos.

The standard library itself provides precedents for this pattern. For example, the exposition-only concept `*indirectly-readable-impl*` ([[iterator.concept.readable]](https://eel.is/c++draft/iterator.concept.readable)) must validate and then repeatedly constrain multiple associated types:

```
template<class In>
  concept indirectly-readable-impl =                    // exposition only
    requires(const In in) {
      typename iter_value_t<In>;
      typename iter_reference_t<In>;
      typename iter_rvalue_reference_t<In>;
      { *in } -> same_as<iter_reference_t<In>>;
      { ranges::iter_move(in) } -> same_as<iter_rvalue_reference_t<In>>;
    } &&
    common_reference_with<iter_reference_t<In>&&, iter_value_t<In>&> &&
    common_reference_with<iter_reference_t<In>&&, iter_rvalue_reference_t<In>&&> &&
    common_reference_with<iter_rvalue_reference_t<In>&&, const iter_value_t<In>&>;
```

In this single definition:

> iter_reference_t<In>
> 
> appears 4 times.
> 
> iter_rvalue_reference_t<In>
> 
> appears 4 times.
> 
> iter_value_t<In>
> 
> appears 3 times.

This repetition is highly error-prone; For user-defined concepts with similar structures but relatively complex, developers must manually re-type or copy-paste these long traits across multiple constraint positions, it is remarkably easy to accidentally omit or misplace critical *cv*-qualifiers or reference decorators (such as `const`, `&`, or `&&`). A single missed ampersand fundamentally changes the concept's semantics, leading to subtle bugs that are difficult to audit.

To eliminate this redundancy and maximize code cohesion, we propose allowing `using` alias syntax directly inside the requires block, with its scope limited to that block. This allows developers to validate a type, bind it to a local name, and apply all subsequent dependent constraints within the same local scope:

```
template<class In>
  concept indirectly-readable-impl =                    // exposition only
    requires(const In in) {
      using value_type       = typename iter_value_t<In>;
      using reference        = typename iter_reference_t<In>;
      using rvalue_reference = typename iter_rvalue_reference_t<In>;
      { *in } -> same_as<reference>;
      { ranges::iter_move(in) } -> same_as<rvalue_reference>;
      requires common_reference_with<reference&&, value_type&>;
      requires common_reference_with<reference&&, rvalue_reference&&>;
      requires common_reference_with<rvalue_reference&&, const value_type&>;
    };
```

Noted that nesting constraints via nested requires statements means they no longer participate in concept subsumption compared to original form, this trade-off is highly beneficial for self-contained helper concepts where partial ordering is not required, since a requirement rarely stops at checking whether a type exists; it almost always serves as a baseline for further verification.

The exact same paradigm applies to more complex, variadic components in the standard library. A prime example is the `*concatable*` concept ([[range.concat.view]](https://eel.is/c++draft/range.concat.view)) in C++26:

```
template<class... Rs>
using concat-reference-t = common_reference_t<range_reference_t<Rs>...>;      // exposition only
template<class... Rs>
using concat-value-t = common_type_t<range_value_t<Rs>...>;                   // exposition only
template<class... Rs>
using concat-rvalue-reference-t =                                             // exposition only
  common_reference_t<range_rvalue_reference_t<Rs>...>;

  
template<class Ref, class RRef, class It>
  concept concat-indirectly-readable-impl =                         // exposition only
    requires (const It it) {
      { *it } -> convertible_to<Ref>;
      { ranges::iter_move(it) } -> convertible_to<RRef>;
    };

template<class... Rs>
  concept concat-indirectly-readable =                              // exposition only
    common_reference_with<concat-reference-t<Rs...>&&,
                          concat-value-t<Rs...>&> &&
    common_reference_with<concat-reference-t<Rs...>&&,
                          concat-rvalue-reference-t<Rs...>&&> &&
    common_reference_with<concat-rvalue-reference-t<Rs...>&&,
                          concat-value-t<Rs...> const&> &&
    (concat-indirectly-readable-impl<concat-reference-t<Rs...>,
                                     concat-rvalue-reference-t<Rs...>,
                                     iterator_t<Rs>> && ...);
                                     
template<class... Rs>
  concept concatable = requires {                                   // exposition only
    typename concat-reference-t<Rs...>;
    typename concat-value-t<Rs...>;
    typename concat-rvalue-reference-t<Rs...>;
  } && concat-indirectly-readable<Rs...>;
  
```

By leveraging local type aliases, this massive, multi-layered infrastructure collapses cleanly into a single, highly cohesive concept definition where types and their dependent requirements are never separated or leaked into the outer scope:

```
template<class... Rs>
  concept concatable = requires {                                   // exposition only
    using reference        = typename common_reference_t<range_reference_t<Rs>...>;
    using value_type       = typename common_type_t<range_value_t<Rs>...>;
    using rvalue_reference = typename common_reference_t<range_rvalue_reference_t<Rs>...>;
    requires common_reference_with<reference&&, value_type&>;
    requires common_reference_with<reference&&, rvalue_reference&&>;
    requires common_reference_with<rvalue_reference&&, value_type const&>;
    requires (requires (const iterator_t<Rs> it) {
      { *it } -> convertible_to<reference>;
      { ranges::iter_move(it) } -> convertible_to<rvalue_reference>;
    } && ...);
  };
```

Under this proposal, the identifier introduced by the `using` alias is only visible within the enclosing `requires`-expression block. If the right-hand side of the `using` declaration fails to substitute , the `requires`-expression evaluates to false, matching the behavior of existing typename requirements. This is a pure syntax extension; since `using` tokens are currently invalid in this context, no existing code will break.

By leveraging type-alias requirements, we can refactor the existing `std::common_with` concept into a single, elegant `requires`-expression. This not only eliminates structural duplication but also aligns the implementation perfectly with its underlying mathematical and narrative constraints:

<!-- tomd:lossy-table -->

```cpp
template<class T, class U>
concept common_with =
  same_as<common_type_t<T, U>, common_type_t<U, T>> &&
  requires {
    static_cast<common_type_t<T, U>>(declval<T>());
    static_cast<common_type_t<T, U>>(declval<U>());
  } &&
  common_reference_with<
    add_lvalue_reference_t<const T>,
    add_lvalue_reference_t<const U>> &&
  common_reference_with<
    add_lvalue_reference_t<common_type_t<T, U>>,
    common_reference_t<
      add_lvalue_reference_t<const T>,
      add_lvalue_reference_t<const U>>>;
```

```cpp
template<class T, class U>
concept common_with = requires {
  using CommonTU = typename common_type_t<T, U>;
  using CommonUT = typename common_type_t<U, T>;
  requires same_as<CommonTU, CommonUT>;

  static_cast<CommonTU>(declval<T>());
  static_cast<CommonTU>(declval<U>());

  using RefT = typename add_lvalue_reference_t<const T>;
  using RefU = typename add_lvalue_reference_t<const U>;
  requires common_reference_with<RefT, RefU>;

  using RefCommon = typename add_lvalue_reference_t<CommonTU>;
  using CommonRef = typename common_reference_t<RefT, RefU>;
  requires common_reference_with<RefCommon, CommonRef>;
};
```

The same architectural simplification also applies to type traits implementation, as this feature is exceptionally useful for defining intricate type traits, particularly those specified with patterns like: "*Let `U` be some type. If `U` is well-formed and certain additional constraints are met, then the type trait denotes `U`*." The standard's definition of `common_reference` (([[meta.trans.other]](https://eel.is/c++draft/meta#trans.other-6))) is precisely such a trait, governed by a highly complex set of conditional bullets:

> -6- For the `common_reference` trait applied to a parameter pack `T` of types, the member `type` shall be either declared or not present as follows:
> 
> 1. […]
> 2. (6.3) — Otherwise, if `sizeof...(T)` is two, let `T1` and `T2` denote the two types in the pack `T`. Then:
>   1. (6.3.1) — Let `R` be `*COMMON-REF*(T1, T2)`. If `T1` and `T2` are reference types, `R` is well-formed, and `is_convertible_v<add_pointer_t<T1>, add_pointer_t<R>> && is_convertible_v<add_pointer_t<T2>, add_pointer_t<R>>` is `true`, then the member typedef `type` denotes `R`.

With the introduction of this feature, we no longer need to translate these narrative rules into heavy, unmaintainable template metaprogramming boilerplate. Instead, we can implement the entire core deduction logic directly and intuitively using `constexpr if` statements with block-scoped aliases:

```
template<class T1, class T2>
consteval auto compute-common-reference() {
    // ...
    // (6.3.1) COMMON-REF check for reference types
    if constexpr (requires {
      using R = typename COMMON-REF(T1, T2); 
      requires is_reference_v<T1>;
      requires is_reference_v<T2>;
      requires is_convertible_v<add_pointer_t<T1>, add_pointer_t<R>>;
      requires is_convertible_v<add_pointer_t<T2>, add_pointer_t<R>>;
    })
      return type_identity<typename COMMON-REF(T1, T2)>{};
    // ...
}
```

As demonstrated above, this feature dramatically unifies the developer experience across both language layers. Whether an implementer is writing a `consteval` type-deduction engine or defining a foundational core `concept`, block-scoped aliases eliminate external boilerplate, safeguard substitution errors through clean short-circuiting, and turn impenetrable metaprogramming into a readable, top-down sequence.

Moreover, The motivation for this feature is reinforced by the introduction of reflection. In C++26, the *type-requirement* production rule already supports the *splice-specifier* syntax, allowing developers to directly validate types generated from meta-objects via `typename [: ... :];`. However, when developers need to perform multiple consecutive constraint checks on the same spliced type, they are currently forced to duplicate the entire dynamic expression repeatedly across the requirement body, introducing substantial structural redundancy.

With this feature, we can significantly simplify such checks by binding the spliced type to a local name at the first introduction. This allows all subsequent constraints to be applied directly to a clean identifier, eliminating the need to re-evaluate or re-spell the complex reflection syntax:

<!-- tomd:lossy-table -->

```cpp
template<typename T> concept C = requires {
  // T::r2 is a reflection of a template Z for which Z<int> is a type
  typename [:T::r2:]<int>;
  requires std::ranges::range<typename [:T::r2:]<int>>;
  requires std::same_as<typename [:T::r2:]<int>::value_type, int>;
};
```

```cpp
template<typename T> concept C = requires {
  // T::r2 is a reflection of a template Z for which Z<int> is a type
  using Container = typename [:T::r2:]<int>;
  requires std::ranges::range<Container>;
  requires std::same_as<Container::value_type, int>;
};
```

```cpp
template<typename T> concept C = requires {
  typename T::method;
  typename [: std::meta::return_type_of(^^T::method) :];
} && 
  std::floating_point<typename [: std::meta::return_type_of(^^T::method) :]>;
```

```cpp
template<typename T> concept C = requires {
  using MethodType = typename T::method;
  using RetType = typename [: std::meta::return_type_of(^^MethodType) :];
  requires std::floating_point<RetType>;
};
```

```cpp
template<typename T> concept C = requires {
  // T::enum_type is a reflection of a enum type
  T::enum_type;
  typename [: std::meta::underlying_type(T::enum_type) :];
} && 
  sizeof(typename [: std::meta::underlying_type(T::enum_type) :]) >= 4;
```

```cpp
template<typename T> concept C = requires {
  // T::enum_type is a reflection of a enum type
  T::enum_type;
  using IntType = typename [: std::meta::underlying_type(T::enum_type) :];
  requires (sizeof(IntType) >= 4);
};
```

In summary, allowing `using` declarations inside the requirement body provides an elegant, unified solution. It ensures that dependent type verification and subsequent semantic constraints remain strictly localized and heavily cohesive.

## Design

### typename is Optional in Local Aliases

A key design question for this feature is whether the `typename` keyword should be required or optional on the right-hand side of a local type alias. We propose the latter.

Forcing users to write the `typename` keyword is unnecessary and introduces redundant boilerplate. In ordinary code outside of constraints, developers can already omit `typename` in contexts that are unambiguously known to be types.

A local alias inside a `requires`-expression behaves exactly like a standard alias declaration in this regard, as the parser already understands that the right-hand side of a `using` statement must resolve to a type. Forcing developers to write `typename` would create an inconsistent user experience for no tangible benefit. Therefore, omitting `typename` must be allowed for these common cases.

### Non-support for Local Alias Templates

We explicitly design this feature to support only local type aliases, excluding local alias templates. Arthur O'Dwyer provided a hypothetical use case where an allocator's rebind structure is bound locally inside a constraint:

```
template<class A>
  concept Allocator = requires (const A& a) {
    template<class T> using rebound = typename std::allocator_traits<A>::template rebind_alloc<T>;
    rebound<int>(a);
    requires std::same_as<typename rebound<int>::value_type, int>;
  };
```

We believe introducing local template templates inside `requires`-expressions adds unnecessary complexity to the parser for little practical gain. If a generic template alias is truly needed, it should be declared externally. If it is only needed for specific validation within the concept, developers can simply bind the concrete instantiated type locally:

```
template<class A>
  concept Allocator = requires (const A& a) {
    using rebound_int = typename std::allocator_traits<A>::template rebind_alloc<int>;
    rebound_int(a);
    requires std::same_as<typename rebound_int::value_type, int>;
  };
```

The primary goal of this proposal is to eliminate text redundancy for deeply nested associated types via simpler typename bindings. Restricting the feature to non-template aliases keeps the implementation footprint minimal for compiler front-ends while still solving the vast majority of real-world pain points.

## Proposed change

This wording is relative to [Lastest Working Draft](https://eel.is/c++draft).

Modify 7.5.8.3 [[expr.prim.req.type]](https://eel.is/c++draft/expr.prim.req.type), Type requirements, as indicated:

> *type-requirement:*
> 
> 1. `typename` <ins>*required-type-id*</ins><del>*nested-name-specifier<sub>opt</sub>* *type-name*</del>`;`
> 2. <del>`typename` *splice-specifier*`;`</del>
> 3. <del>`typename` *splice-specialization-specifier*`;`</del>
> 4. <ins>*type-alias-requirement*</ins>
> 
> <ins>*type-alias-requirement:*</ins>
> 
> 1. <ins>`using` *identifier* `=` `typename`<sub>opt</sub> *required-type-id* `;`</ins>
> 
> <ins>*required-type-id:*</ins>
> 
> 1. <ins>*nested-name-specifier<sub>opt</sub>* *type-name*</ins>
> 2. <ins>*splice-specifier*</ins>
> 3. <ins>*splice-specialization-specifier*</ins>

> > -1- A *type-requirement* asserts the validity of a type. <ins>A *type-alias-requirement* introduces a new alias name into the enclosing block scope that denotes the valid type.</ins> The component names of a *type-requirement* <ins>that is not a *type-alias-requirement*</ins> are those of <ins>its *required-type-id*</ins><del>its *nested-name-specifier* (if any) and *type-name* (if any)</del>.
> > 
> > [*Note 1*: The enclosing *requires-expression* will evaluate to `false` if substitution of template arguments fails<ins>, which, for a *type-alias-requirement*, includes the substitution into the type denoted by the alias</ins>. — *end note*]
> > 
> > [*Example 1*:
> > 
> > ```
> > template<typename T, typename T::type = 0> struct S;
> > template<typename T> using Ref = T&;
> > 
> > template<typename T> concept C = requires {
> >   typename T::inner;        // required nested member name
> >   typename S<T>;            // required valid template-id; fails if T::type does not exist as a type
> >   typename Ref<T>;          // required alias template substitution, fails if T is void
> >   typename [:T::r1:];       // fails if T::r1 is not a reflection of a type
> >   typename [:T::r2:]<int>;  // fails if T::r2 is not a reflection of a template Z for which Z<int> is a type
> > 
> >   using U = T::inner;              // OK: U denotes T::inner if valid; typename omitted
> >   using V = typename T::inner;     // OK: explicit typename is also allowed
> >   requires std::same_as<U, int>;   // OK: U is in scope
> >   template<class X> using W = X&;  // error: template declaration is not allowed within a type-requirement
> > 
> >   using R1 = [:T::r1:];            // OK: R1 denotes the spliced type if valid
> >   requires requires {
> >     requires sizeof(R1) == 1;      // OK: R1 is in scope
> >   };
> > };
> > ```
> > 
> > — *end example*]
> > 
> > -2- A *type-requirement* that names a class template specialization does not require that type to be complete ([basic.types.general]).
