← SEARCHABLE ARCHIVE
C / C++ · 2026-08-10

从 C++ 基础到掌握 — 第 16 集

使用 Concepts 和 requires 表达模板要求

INTERACTIVE READING MODE →

本博客文章以教学为目的编写。 内容可能存在错误,请结合目标编译器和标准库文档进行确认。

模板能把同一规则应用到多种类型,但不支持的类型往往会在实现深处产生冗长错误。 C++20 的 Concepts 和 requires 可以把要求写进模板的公开声明。

无约束模板的问题

template <typename T>
int GetHealth(const T& value)
{
    return value.Health();
}

这个函数只能用于可调用 Health() 的类型,但签名没有表达该条件。编译器必须实例化函数体后才会发现问题。

用 Concept 命名要求

#include <concepts>

template <typename T>
concept Damageable = requires(T value, int amount)
{
    { value.Health() } -> std::convertible_to<int>;
    { value.ApplyDamage(amount) } -> std::same_as<void>;
};

template <Damageable T>
void Attack(T& target, int damage)
{
    target.ApplyDamage(damage);
}

requires expression 不执行代码,只检查语法和结果类型是否有效。现在调用前就能看到 Attack 的契约,错误也会更靠近调用位置。

组合类型条件

template <typename T>
concept SmallId = std::integral<T> && (sizeof(T) <= 4);

template <SmallId T>
void WriteId(T id);

Concept 可以用逻辑运算组合 type trait 和 requires,约束还会参与重载排序。

void Print(std::integral auto value);
void Print(std::floating_point auto value);

带 Concept 的 auto 是函数模板的简写。当整数和浮点数的语义不同时,分离重载通常比在一个函数中堆叠 if constexpr 更清晰。

语法有效不等于语义正确

a < b 能编译,不代表比较一定形成一致顺序;语法上可复制,也不保证两个副本在行为上彼此独立。

标准 Concepts 除语法外还记录语义要求。自定义 Concept 应使用有意义的名称,关键语义仍需测试。

不要添加超过实现所需的约束。应表达最小且真实的契约,避免在没有提升正确性的情况下拒绝可用类型。