跳转至

requires 表达式

标准 难度 状态 分级 证据 审阅 最后校验
C++20 3/5 final A 3 条(manual 3) 单审(zyuhao7) 2026-10-26

编译器支持:GCC 10+ · Clang 10+ · MSVC 19.30+。版本查询:cppreference 编译器支持。

主张与证据

主张 支持证据
用 requires 表达式检测成员是否存在,并在 if constexpr 中据此选择分支 ev-mold-requires-expression-unit-type-001
用 requires 表达式把静态成员存在性封装成可复用的 concept ev-mold-requires-expression-concepts-002
解构器测试覆盖 lambda 签名中 requires 表达式的修饰名 ev-abseil-requires-expression-mangling-003

本站证据页:ev-abseil-requires-expression-mangling-003 · ev-mold-requires-expression-concepts-002 · ev-mold-requires-expression-unit-type-001

解决什么问题

C++17 之前,探测“某个类型是否支持某个操作”通常要写 SFINAE:std::void_t、decltype、enable_if 和重载一起使用。表达式能编译就选中某个重载,不能编译就替换失败。这种写法能工作,但嵌套深、错误信息长,而且约束的意图藏在类型系统里。

requires 表达式把“这些表达式是否合法”直接写成一个布尔值。它不执行表达式,只检查语法和语义有效性,可以放在 static_assert、if constexpr 或概念定义中使用。

基础用法

#include <concepts>
#include <cstddef>
#include <iostream>

template <typename T>
concept HasValue = requires(T t) {
  t.value;
};

template <typename T>
concept Sized = requires(const T &t) {
  typename T::value_type;
  { t.size() } -> std::convertible_to<std::size_t>;
};

struct Entry {
  using value_type = int;
  int value;
  std::size_t size() const { return 1; }
};

struct Empty {};

int main() {
  std::cout << HasValue<Entry> << ' ' << HasValue<Empty> << ' '
            << Sized<Entry> << ' ' << Sized<Empty> << '\n';
}

requires(T t) { ... } 的参数是假设变量,只用于书写检查表达式,不会真的构造对象。t.value; 是简单要求,只检查成员访问是否合法;typename T::value_type; 是类型要求;{ t.size() } -> std::convertible_to<std::size_t>; 是复合要求,同时检查表达式合法且返回类型可转换。

进阶用法

requires 表达式通常包装成 concept,再用于约束重载。复合要求可以同时检查表达式合法、返回类型可转换,以及是否 noexcept。

template <typename T>
concept Sized = requires(const T &value) {
  typename T::value_type;
  { value.size() } -> std::convertible_to<std::size_t>;
};

template <Sized T>
std::size_t length(const T &value) {
  return value.size();
}

template <typename T>
std::size_t length(const T &) {
  return 0;
}

Sized<T> 为真时,第一个重载比无约束版本更特殊,重载选择会优先选它;不满足时退回到返回 0 的版本。整个过程只检查表达式能否编译,不会调用 value.size(),因此 requires 表达式不能替代运行期前置条件检查。

完整示例见 examples/cpp20/requires_expressions.cpp。

真实项目证据

项目 位置 用法 为什么典型
mold src/gdb-index.cc#L188-L192 用 requires { hdr.unit_type; } 判断 DWARF 头是否有该字段 不同 DWARF 版本字段不同,条件表达式让旧格式仍能参与编译
mold src/elf.h#L1796-L1798 用 requires { E::R_IRELATIVE; } 等定义架构能力概念 把“某架构是否提供某个静态成员”封装成概念,后续约束直接复用
abseil-cpp absl/debugging/internal/demangle_test.cc#L295-L300 用测试覆盖 lambda 签名中 requires 表达式的修饰名 requires 表达式进入符号修饰规则,解构器必须能处理嵌套检查

三条证据均为 manual 级:作者复核,尚未引入第二名审核者。前两条来自 mold 生产路径,第三条是 Abseil 解构器测试,覆盖 requires 表达式在修饰名中的表示。

陷阱与边界

requires 表达式不是 requires 子句。 requires { ... } 求值为 bool,是一个表达式;template <typename T> requires C<T> 是约束子句。两者可以组合,但语法位置和作用不同。

它检查“能否编译”,不检查语义是否正确。 requires(T t) { t.size(); } 只要求 size() 可调用;返回类型不对、返回垃圾值、运行期抛异常都不影响结果。需要约束返回类型时,写成复合要求 { t.size() } -> std::same_as<std::size_t>;。

表达式不会被求值。 requires(T t) { t.pop(); } 不会真的调用 pop(),也不会检查容器是否为空。依赖运行期状态的前置条件,requires 表达式表达不了。

复合要求可以检查 noexcept。 { expr } noexcept -> Concept; 同时要求表达式不抛异常并满足返回类型约束。只写 { expr } -> Concept; 不会检查异常规格。

类型要求和表达式要求分开写。 typename T::value_type; 检查嵌套类型是否存在;去掉 typename 后含义会变成表达式检查。两者可以组合,但不要把它们当成同一种要求。

requires 表达式不能吞掉所有错误。 它把紧邻的无效表达式变成“不满足”,但模板实例化深处的硬错误仍可能直接报错。约束应尽量写在接口层,而不是依赖 requires 捕获所有实现细节。

概念约束按从左到右的合取求值。 C<T> && requires { ... } 中,左侧不满足时右侧不会继续检查。这个顺序可以用来先排除明显不合适的类型,但要避免把副作用写进约束。

库联动

  • mold:用简单要求检测 DWARF 头字段,并把 ELF 架构是否提供重定位静态成员封装成可复用概念。
  • Abseil:解构器测试覆盖 requires 表达式进入函数签名修饰名后的符号,保证调试输出能还原 lambda 签名。
  • 与 requires 子句的分工:requires 表达式求值为 bool,可以放在 static_assert、if constexpr 或概念定义中;requires 子句用于约束模板或函数,两者可以组合,但语法位置不同。

横向对比

方案 表达位置 错误信息 可复用性 主要代价
SFINAE / enable_if 返回类型或模板参数 冗长,指向替换失败 中 语法复杂,约束意图不直观
检测惯用法(void_t) 类型特征 中等 高 需要为每种能力写 trait,样板代码多
if constexpr 探测 函数体 局部较清晰 低 只能影响当前函数,不能约束重载
requires 表达式 概念、if constexpr、static_assert 较清晰 高 C++20 起可用;只检查有效性,不检查语义

跨标准演进:C++20 通过 P0734R0 引入 concepts 与 requires 表达式,取代了大部分 SFINAE 检测惯用法。截至 C++23,这套核心规则没有变化;后续的约束设施仍建立在 requires 表达式之上。