C++之概念Concept

32 阅读5分钟

Concepts(概念)是 C++20 核心特性之一,用于给模板参数添加显式、可读、编译期类型约束。

C++20 之前,模板约束主要依赖:

  • SFINAE(Substitution Failure Is Not An Error)
  • std::enable_if
  • traits + 偏特化

这些方案的核心问题:

问题描述
可读性差模板签名被污染
错误信息差编译报错极其难读
语义不清无法表达“意图”(只是技巧)

基础

Concept解决:约束前置、错误清晰、语义显式、语法简洁。

基本定义

template<typename T>
concept 概念名 = 编译期布尔表达式;
  • concept:关键字,定义命名的编译期谓词
  • 表达式:true/false,决定类型是否满足约束

整数概念示例

#include <type_traits>
template<typename T>
concept Integral = std::is_integral_v<T>; // T 是整型

概念组合

概念本质上是一个编译期布尔谓词,可直接用逻辑运算符组合,形成更复杂的概念。

template<typename T>
concept Integral = std::is_integral_v<T>;

template<typename T>
concept FloatingPoint = std::is_floating_point_v<T>;

// 析取(或):既是整数 或 浮点数
template<typename T>
concept Numeric = Integral<T> || FloatingPoint<T>;

// 合取(与):整数且是有符号的(但标准库已经提供更好的写法)
template<typename T>
concept SignedInt = Integral<T> && std::is_signed_v<T>;

// 否定:不是整数
template<typename T>
concept NonIntegral = !Integral<T>;

概念细化

细化类似“继承”:所谓的“继承”是指一个概念在另一个概念的基础上添加额外的约束,形成一条概念层次链

template<typename T>
concept Animal = requires(T a) {
    { a.speak() } -> std::convertible_to<std::string>;
};

template<typename T>
concept Dog = Animal<T> && requires(T d) {
    d.wag_tail();
    requires std::same_as<decltype(d.speak()), std::string>; // 细化了返回值
};

Dog 概念细化了 Animal:凡是满足 Dog 的类型,必然满足 Animal

requires 表达式

requires 用于描述类型必须支持的语法 / 接口:

  1. 编译期约束模板参数
    只有满足 requires 条件的类型或表达式,模板才能被实例化,否则直接编译报错,且错误信息远比传统 SFINAE 清晰。
  2. 改善重载解析
    多个模板重载时,满足不同 requires 的版本会被精确选择,可实现类似“类型模式匹配”的效果。
  3. 表达对类型能力的精确要求
    可以检查:某个表达式是否合法、某个类型是否存在、某个成员函数返回值是否满足某概念、某个操作是否不抛异常等。

requires 表达式内的需求序列默认是逻辑与:列出的每一项都必须满足,整个表达式才返回 true。需要有多个 requires 表达式(或概念)在外层用 &&|| 拼接;或者嵌套:

template<typename T>
concept MyConcept = requires(T x) {
    x.foo();
    requires std::copyable<T>;
    requires sizeof(T) == 4;
};

两种形态

requires clause(约束子句)

写在模板参数列表后、函数/类后面,用于“开关”模板。当 布尔常量表达式(通常是概念检查)true 时模板启用。

template<typename T>
requires 布尔常量表达式
void func(T t);

也可以将 requires 放在函数尾部(更常见于类模板成员):

template<typename T>
T add(T a, T b) requires std::integral<T> 
{ return a + b; }

concept 本质上就是产生布尔值的 constexpr 表达式,所以可直接使用:

template<std::integral T>    // 简写,等价于 requires std::integral<T>
T add(T a, T b)
{ return a + b; }

requires 子句的典型用途:约束函数模板、类模板、成员函数。

requires expression(约束表达式)

用于定义 concept 的“检测器”

requires (参数列表) { 需求序列; }

参数列表可省略,需求序列是一组对类型、表达式、嵌套条件的要求。requires 表达式本身返回 bool(纯右值),因此可直接作为 concept 的定义体。

定义一个 concept,检查类型是否可相加:

template<typename T>
concept Addable = requires(T a, T b) {
    a + b;           // 简单需求:a+b 必须是合法表达式
};

四类 requirement

1)简单需求

只检查表达式是否合法

template<typename T>
concept Addable = requires(T a, T b) {
    a + b;           // 支持 +
    a += b;          // 支持 +=
};

2)类型需求

检查某个类型是否存在

template<typename T>
concept HasValueType = requires {
    typename T::value_type; // 有内嵌类型 value_type
};

3)成员需求

检查是否有指定成员

template<typename T>
concept Drawable = requires(T t) {
    t.draw();               // 有 draw() 成员函数
    { t.size() } -> std::same_as<size_t>; // size() 返回 size_t
};

4)复合需求

多种需求组合在一起

template<typename T>
concept Container = requires(T c) {
    typename T::iterator;
    c.begin();
    c.end();
    c.size();
} && std::copyable<T>; // 且满足可拷贝

编译器分支

if constexpr:编译期分支判断(不同类型走不同逻辑)

  • 只在编译时判断: 条件必须是编译期常量
  • 不满足的分支直接被丢弃,不参与编译、不生成代码;
  • 无任何运行时开销
  • 可嵌套、可和模板配合
if constexpr (编译期布尔表达式) {
    // 满足时编译else {
    // 不满足时编译
}

与概念结合,增强模板功能

#include <iostream>
#include <type_traits>
using namespace std;

// 1. 定义概念:只允许数字类型
template<typename T>
concept Number = is_arithmetic_v<T>;

// 2. 约束模板
template<Number T>
auto smart_add(T a, T b) {
    // 3. 编译期分支
    if constexpr (is_integral_v<T>) {
        cout << "[整数运算] ";
        return a + b;
    } else {
        cout << "[浮点数运算] ";
        return a + b;
    }
}

int main() {
    cout << smart_add(23) << endl;    // 整数
    cout << smart_add(2.13.2) << endl;// 浮点数
    // smart_add("abc", "def"); // ❌ 直接被 Concept 拦截,报错清晰
}

标准库

<concepts> 头文件中提供了大量常用概念

语言核心概念

  • std::same_as<T, U> —— 严格相同类型
  • std::derived_from<Derived, Base> —— 派生关系
  • std::convertible_to<From, To> —— 隐式转换或与 To 相同
  • std::common_with<T, U> —— 有公共类型
  • std::integral<T> —— 整数类型
  • std::signed_integral<T> —— 有符号整数
  • std::unsigned_integral<T> —— 无符号整数
  • std::floating_point<T> —— 浮点类型

对象构造 / 赋值的概念

  • std::destructible<T>
  • std::constructible_from<T, Args...>
  • std::default_initializable<T>
  • std::move_constructible<T>
  • std::copy_constructible<T>
  • std::movable<T> —— 可移动 + 可交换
  • std::copyable<T> —— 可拷贝 + 可移动

比较相关

  • std::equality_comparable<T> —— 支持 ==,且结果为 bool
  • std::totally_ordered<T> —— 支持 < <= > >=

调用相关

  • std::invocable<F, Args...> —— 可调用
  • std::regular_invocable<F, Args...> —— 等价调用(无副作用)

对象特性

  • std::semiregular<T> —— 可默认构造、拷贝、赋值
  • std::regular<T> —— semiregular + equality_comparable