C++ Concepts: Nested Typename Type Requirements

In C++20, concepts allow developers to define compile-time constraints on template arguments, ensuring templates are instantiated only with types that meet specific interfaces. One key feature of the requires expression is the type requirement, which uses the typename keyword to assert that a particular nested type or type alias exists within a given type. This article explains how type requirements work, their syntax, why the typename keyword is necessary, and how to constrain nested types within modern C++ template metaprogramming.

The Purpose of Type Requirements

Generic code frequently relies on associated types defined inside class templates—such as standard container member types like value_type, iterator, or allocator_type. Before concepts, checking for the existence of these nested typenames required verbose SFINAE (Substitution Failure Is Not An Option) techniques like std::void_t.

With C++20 concepts, you can directly verify the validity of a nested type using a type requirement inside a requires clause. If the specified type does not exist, the concept simply evaluates to false instead of triggering a hard compiler error.

Basic Syntax

A type requirement begins with the typename keyword, followed by an qualified identifier.

#include <concepts>
#include <vector>

template <typename T>
concept HasValueType = requires {
    typename T::value_type;
};

static_assert(HasValueType<std::vector<int>>); // true: std::vector defines value_type
static_assert(!HasValueType<int>);             // true: int has no nested value_type

In the example above, typename T::value_type; instructs the compiler to check whether T possesses an accessible nested type, type alias, or using-declaration named value_type.

Why the typename Keyword is Mandatory

Within a requires expression, statements without leading keywords are parsed as simple requirements, which treat the statement as an expression:

template <typename T>
concept FlawedCheck = requires {
    T::value_type; // Treated as an expression (e.g., checking for a static member variable)
};

If value_type is a type alias rather than a static member variable, T::value_type; is ill-formed as an expression, causing the constraint check to fail. Adding the typename keyword explicitly tells the compiler that the identifier refers to a type, turning the statement into a type requirement.

Checking Nested Template Types

When a nested type is itself a template, the template disambiguator must be used alongside typename to avoid parsing ambiguity:

template <typename T>
concept HasRebind = requires {
    typename T::template rebind<int>;
};

This requirement asserts that T has a nested class template or template alias named rebind that can be successfully instantiated with int.

Combining Type Existence with Further Constraints

Asserting that a nested type exists is often only the first step; generic algorithms frequently need the nested type to fulfill additional properties. This can be accomplished by combining the type requirement with a nested requirement or by applying another concept directly to the nested type:

#include <concepts>

template <typename T>
concept HasIntegralValueType = requires {
    typename T::value_type;
    requires std::integral<typename T::value_type>;
};

Alternatively, you can apply standard concepts directly outside the requires block when the type requirement is used as a predicate:

template <typename T>
concept ContainerWithIntegralValues = HasValueType<T> && std::integral<typename T::value_type>;

Summary of Rules

  • Syntax: Always write typename T::nested_name; inside a requires block to assert a type's existence.
  • Failure Handling: If the nested typename is absent, private, or ambiguous, the requirement fails substitution and the concept evaluates to false.
  • Templates: Use typename T::template nested_template<Args...>; when querying member templates.