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_typeIn 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 arequiresblock 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.