Skip to content

C++20 concepts(约束与概念):语法、与 SFINAE 对比

一、为什么需要 concepts

模板报错晦涩 + enable_if 语法冗长可读性差。C++20 引入命名好的编译期谓词(concept),把“类型须满足的性质”提到接口上,报错变成“不满足概念 X”,也更可读地约束模板。同时也是其它高级特性的基础(ranges 由 concept 驱动)。

二、定义 concept + requires 子句

定义概念

cpp
// 简单概念:为谓词起名
template <typename T>
concept Integral = std::is_integral_v<T>;

// requires 表达式:要求某些表达式合法(探测的“官方”写法)
template <typename T>
concept HasSize = requires(T& t) { t.size(); };   // 表达式合法即可

带返回类型约束:{ t.size() } -> std::convertible_to<std::size_t>;

约束模板的四种写法

cpp
// 1) 函数参数上用 concept(最可读)
template <Integral T> 
T square(T x) { return x*x; }

Integral auto half(Integral auto x);       // 缩写(constrained auto)

// 2) requires 子句尾部
template <typename T> requires Integral<T>
void f(T);

// 3) requires 嵌在 decltype/后置
template <typename T> void g(T) requires Integral<T>;

三、概念是“半开的谓词”,可组合

cpp
template<typename T> concept Number = Integral<T> || FloatingPoint<T>;
template<typename T> concept TerseThreadsafe = requires(...) { ...; };

概念的结果是编译期 bool 常量,可 &&/||/! 组复合,也可 static_assert(Concept<T>) 自测。

四、requires 表达式种类(detail,能报出更精准错误)

  • 简单要求 t.size();
  • 类型要求 typename T::value_type;
  • 复合要求 { expr } noexcept -> Ret
  • 嵌套要求 requires C<A>;

面试若只让讲一层,讲「concepts 命名 + requires 做可读约束、可组合、报错清晰」足够;能做 examples 深讲加分。

五、concepts vs SFINAE/enable_if 对比(重要)

SFINAE/enable_ifconcepts/requires
可选性表达冗长的 enable_if+x 组合简洁、按名组合
诊断“no matching function…候选被排弃”(不强调为何)“约束 X 未满足”指向明确概念
约束检查时机替换过程中语义前后、更清晰约束
表达“特性”用 trait用 concept(本质包装 trait+requires)
可靠性手动管理歧义requires 提供子集合规约(更精确)
编译速度有时慢相近,诊断更好

关键收获:concept 本质还是编译期布尔谓词——其实现仍基于 type traits + requires 探测,可视为“给 SFINAE 能力披上可读接口 + 可复用命名”。

六、与 overload / 分派

concepts 可让重载按“更精炼概念”排序:如两个概念 NumberIntegral,传入 int 都匹配…… 不总按条件简繁自动排序;规则是按概念约束**更严格(subsumption 包含关系)**者胜出,需小心定义保证者胜。题目常直接给两 concept 判断走哪个——记住“概念封装更紧(后继)的优先”。

七、示例应用(HPC 语境)

  • 约束矩阵 Element 满足 requires(T& a){ a+a; a*a; } → 泛型 kernel 需要可算术。
  • ranges 视图靠 concepts 选迭代器类别管道与算法。
  • 若团队要兼容旧编译器,可退回 enable_if/if constexpr;concept 需 C++20。

八、自测

  1. 写个 Addable concept 要求 a+b 合法。
  2. template<class T> requires std::integral<T> void f(); 与未 constrained f 同时存在时传 int 调哪个?—— constrained 版本优先(更受约束)。
  3. concepts 是否运行期开销?—— 纯编译期,零运行成本。

C++ 面试八股 · VitePress 版