Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

现代 C++ / Modern C++

1. 现代 C++ 总览:不是特性清单,而是表达能力 / Modern C++ Overview

2. 移动、转发与值类别:std::move 为什么不移动 / Move, Forward, and Value Categories

3. 类型推导、初始化与 constexpr / Type Deduction, Initialization, and constexpr

4. 模板、实例化与 SFINAE / Templates, Instantiation, and SFINAE

5. concept 与约束:把模板要求写在接口上 / Concepts and Constraints

6. 异常、RAII 与 noexcept:失败路径也要清理 / Exceptions, RAII, and noexcept

7. lambda、捕获与生命周期:回调不是免费午餐 / Lambdas, Captures, and Lifetime

8. callable wrapper 与 std::invoke:怎样保存和调用行为 / Callable Wrappers and std::invoke

9. 词汇类型与错误建模:让接口自己说话 / Vocabulary Types and Error Modeling

10. 协程与异步抽象:先理解状态机 / Coroutines and Async Abstractions

本页目录

concept 与约束:把模板要求写在接口上 / Concepts and Constraints ​

concept 的目的不是让代码看起来更新。

它解决的是模板接口不清楚的问题:这个模板到底需要类型具备什么能力?

1. 没有约束时,要求藏在函数体里 ​

看函数模板:

cpp
#include <iostream>

template <class T>
void print_size(T const& x) {
    std::cout << x.size() << '\n';
}
1
2
3
4
5
6

接口表面上是:

text
任何 T 都可以
1

但函数体真正要求:

text
T 类型对象必须有 size()
1

如果调用:

cpp
print_size(42);
1

错误会在实例化函数体时爆出来。

也就是说,调用者直到出错才知道这个模板需要什么能力。

concept 的思路是:

text
把要求从函数体里搬到接口上。
1

2. 最小 concept ​

cpp
#include <concepts>
#include <iostream>
#include <string>

template <class T>
concept HasSize = requires(T x) {
    x.size();
};

template <HasSize T>
void print_size(T const& x) {
    std::cout << x.size() << '\n';
}

int main() {
    std::string text = "hello";
    print_size(text);
    // print_size(42);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

HasSize 的读法是:

text
对某个 T 类型对象 x,
表达式 x.size() 必须合法。
1
2

图:

text
call print_size(arg)
  |
  v
check HasSize<type of arg>
  |
  +-- satisfied -> instantiate function
  |
  +-- not satisfied -> report constraint failure
1
2
3
4
5
6
7
8

这比在函数体深处报错更接近接口错误。

3. requires 可以检查表达式 ​

简单约束:

cpp
template <class T>
concept Addable = requires(T a, T b) {
    a + b;
};
1
2
3
4

它只要求表达式 a + b 合法。

如果你还想检查返回类型:

cpp
#include <concepts>

template <class T>
concept AddableToSelf = requires(T a, T b) {
    { a + b } -> std::same_as<T>;
};
1
2
3
4
5
6

读法:

text
a + b 必须合法
并且结果类型必须正好是 T
1
2

这里的 { expr } -> constraint; 是 requires 表达式里的复合要求。

4. 使用标准 concept ​

标准库已经提供一些常用 concept。

例如:

cpp
#include <concepts>
#include <iostream>

void show(std::integral auto value) {
    std::cout << "integer: " << value << '\n';
}
1
2
3
4
5
6

std::integral 表示整数类型。

调用:

cpp
show(42);
// show(3.14); // double 不满足 integral
1
2

还有:

text
std::floating_point
  浮点类型

std::same_as<A, B>
  两个类型相同

std::derived_from<D, B>
  D 派生自 B

std::convertible_to<From, To>
  From 可以转换为 To
1
2
3
4
5
6
7
8
9
10
11

这些不是背单词。

它们是让接口更接近自然语义。

5. 约束参与重载选择 ​

看代码:

cpp
#include <concepts>
#include <iostream>

void describe(auto const&) {
    std::cout << "general value\n";
}

void describe(std::integral auto value) {
    std::cout << "integer: " << value << '\n';
}

int main() {
    describe(1);
    describe(3.14);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

结果是:

text
1
  满足 integral,选择更具体版本

3.14
  不满足 integral,选择通用版本
1
2
3
4
5

约束不是注释。

它参与编译器选择。

6. concept 不能证明所有语义 ​

concept 能检查语法和部分类型关系。

但它不能自动证明业务语义。

例如排序需要比较操作:

cpp
template <class T>
concept LessComparable = requires(T a, T b) {
    { a < b } -> std::convertible_to<bool>;
};
1
2
3
4

这能检查 a < b 是否存在。

但它不能证明 < 满足严格弱序。

严格弱序大致要求:

text
不能 a < a
如果 a < b,则不能 b < a
比较关系要能稳定传递
等价关系要一致
1
2
3
4

这些语义需要程序员和测试保证。

所以 concept 是接口边界,不是数学证明器。

7. 不要把 concept 写得过细 ​

糟糕的约束:

cpp
template <class T>
concept MyContainer = requires(T x) {
    x.begin();
    x.end();
    x.size();
    x.reserve(10);
    x.capacity();
    x.data();
    x.push_back(1);
    x.clear();
};
1
2
3
4
5
6
7
8
9
10
11

这个 concept 可能把接口限制得过死。

你要问:

text
这个函数真正需要什么能力?
只需要遍历?
需要随机访问?
需要能添加元素?
需要元素连续存储?
需要知道大小?
1
2
3
4
5
6

如果只需要遍历,就不要要求 reserve()。

这和普通函数参数设计一样:

text
接口应该要求足够用的最小能力。
1

8. concept 和文档 ​

concept 不能代替文档。

但它能让文档更有根。

例如:

cpp
template <class R>
concept StringRange = requires(R r) {
    r.begin();
    r.end();
};
1
2
3
4
5

这还不够好,因为没有表达元素类型。

更完整的约束可能需要 ranges 库。

但即使如此,概念名也应该承担解释职责:

text
HasSize
Addable
Sortable
ReadableBuffer
OutputIterator
1
2
3
4
5

名字不要写成:

text
GoodType
ValidThing
MyConcept
1
2
3

调用者看不出真实要求。

9. 本篇总结 ​

concept 的核心价值:

text
把模板要求写在接口上
让错误更早、更清楚
让重载选择更可控
让泛型代码更接近普通接口设计
1
2
3
4

使用 concept 时问:

text
这个模板真正需要类型具备什么能力?
这个约束是否太宽或太窄?
失败时错误信息能不能帮助调用者?
concept 名字是否说明了语义?
1
2
3
4

下一篇讲异常、RAII 和 noexcept。现代 C++ 不只关心“模板能不能编译”,也关心“失败时资源能不能被正确清理,接口有没有承诺不抛异常”。

最后更新于:

Pager
上一篇4. 模板、实例化与 SFINAE / Templates, Instantiation, and SFINAE
下一篇6. 异常、RAII 与 noexcept:失败路径也要清理 / Exceptions, RAII, and noexcept

持续记录,持续成长

Copyright © Tidenflow