Skip to content

Commit fa35089

Browse files
feat(vol4): 元编程子卷 concepts 三连
vol4-advanced/vol3-metaprogramming-cpp20-23 子卷前三篇: - 01 Concepts 详解(四种语法形式/enable_if 报错对比/标准库概念) - 02 约束模板(subsumption 与重载/原子约束真包含) - 03 requires 表达式(四种成分/不求值/硬错误坑) 配套 9 个示例;报错演示用 -D 宏切换。index 改正式导航。
1 parent 80dc435 commit fa35089

14 files changed

Lines changed: 962 additions & 14 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
---
2121

2222
<!-- COVERAGE_START -->
23-
![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 602/604 docs translated
23+
![English Coverage](https://img.shields.io/badge/en_coverage-99%25-green.svg) 602/607 docs translated
2424
<!-- COVERAGE_END -->
2525

2626
## 这是什么项目
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Concepts 的三个经典坑,用宏切换复现(默认编译干净,加宏触发对应坑的编译失败)
2+
// 对应文章:02-constraining-templates.md、03-requires-expressions.md
3+
//
4+
// 默认编译(干净,main 演示「用 concept 包装优雅判断负例」的解法):
5+
// g++ -Wall -Wextra -std=c++20 concept_pitfalls.cpp -o cp && ./cp
6+
// 复现三个坑(每个都会编译失败,对照文章看报错):
7+
// g++ -std=c++20 -DDEMO_AMBIGUITY concept_pitfalls.cpp # 坑一:两个互不蕴含的 concept 重载,Duck
8+
// 同时满足 -> 歧义 g++ -std=c++20 -DDEMO_NOSUBSUME concept_pitfalls.cpp # 坑二:C2=C1<T>
9+
// 规范化后原子约束与 C1 相同,不 subsume -> 歧义 g++ -std=c++20 -DDEMO_HARD_ERROR
10+
// concept_pitfalls.cpp # 坑三:对具体类型直接写 requires 表达式 -> 硬错误
11+
#include <iostream>
12+
#include <string>
13+
14+
// 坑一用:两个彼此独立的 concept,谁也不蕴含谁
15+
template <typename T>
16+
concept Swimmable = requires(T t) { t.swim(); };
17+
template <typename T>
18+
concept Flyable = requires(T t) { t.fly(); };
19+
void act(Swimmable auto) {}
20+
void act(Flyable auto) {}
21+
22+
// 坑二用:C2 只是 C1<T> 换名,没有额外原子约束
23+
template <typename T>
24+
concept C1 = requires(T t) { t.a(); };
25+
template <typename T>
26+
concept C2 = C1<T>;
27+
void g(C1 auto) {}
28+
void g(C2 auto) {}
29+
30+
// 坑三的解法:把 requires 表达式包进 concept,求值时 T 是模板参数 -> SFINAE 友好,失败返回 false
31+
// 而非硬错误
32+
template <typename T>
33+
concept HasNope = requires(T t) { t.nope(); };
34+
35+
struct Duck {
36+
void swim() {}
37+
void fly() {}
38+
};
39+
struct X {
40+
void a() {}
41+
};
42+
43+
int main() {
44+
std::cout << std::boolalpha;
45+
// 解法演示:concept 包装后,不存在的成员优雅返回 false
46+
std::cout << "HasNope<std::string>: " << HasNope<std::string> << "\n"; // false,不硬错误
47+
48+
#if DEMO_AMBIGUITY
49+
act(Duck{}); // Duck 同时满足 Swimmable 和 Flyable,两者互不蕴含 -> 编译器选不出 -> 歧义
50+
#elif DEMO_NOSUBSUME
51+
g(X{}); // X 同时满足 C1 和 C2,但 C2 规范化后原子约束= C1,无真包含 -> 歧义
52+
#elif DEMO_HARD_ERROR
53+
static_assert(!requires(std::string s) { s.nope(); }); // 对具体类型 string 直接写 -> 硬错误
54+
#endif
55+
return 0;
56+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Concepts 的四种语法形式:把约束写进模板参数、requires 子句、简写 auto、内嵌 requires 表达式
2+
// 对应文章:documents/vol4-advanced/vol3-metaprogramming-cpp20-23/01-concepts.md
3+
// 编译运行:g++ -Wall -Wextra -std=c++20 concepts_four_forms.cpp -o four_forms && ./four_forms
4+
#include <concepts>
5+
#include <iostream>
6+
#include <string>
7+
8+
template <typename T>
9+
concept Numeric = std::integral<T> || std::floating_point<T>;
10+
11+
// 形式①:把 concept 直接当约束写在模板参数列表里
12+
template <Numeric T> T form1(T a, T b) {
13+
return a + b;
14+
}
15+
16+
// 形式②:requires 子句(trailing requires-clause)
17+
template <typename T>
18+
requires Numeric<T>
19+
T form2(T a, T b) {
20+
return a + b;
21+
}
22+
23+
// 形式③:简写模板语法(constrained auto)
24+
auto form3(Numeric auto a, Numeric auto b) {
25+
return a + b;
26+
}
27+
28+
// 形式④:模板参数列表后跟 requires,内层是一个 requires 表达式
29+
template <typename T>
30+
requires requires(T x) { x + x; }
31+
T form4(T a, T b) {
32+
return a + b;
33+
}
34+
35+
int main() {
36+
std::cout << "form1: " << form1(3, 5) << "\n";
37+
std::cout << "form2: " << form2(2.0, 3.0) << "\n";
38+
std::cout << "form3: " << form3(10, 20) << "\n";
39+
std::cout << "form4: " << form4(std::string("a"), std::string("b")) << "\n";
40+
return 0;
41+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// concept 约束可以用在函数模板、类模板、成员函数、简写 auto 等多种位置
2+
// 对应文章:documents/vol4-advanced/vol3-metaprogramming-cpp20-23/02-constraining-templates.md
3+
// 编译运行:g++ -Wall -Wextra -std=c++20 constraints_everywhere.cpp -o ce && ./ce
4+
#include <concepts>
5+
#include <iostream>
6+
7+
template <typename T>
8+
concept Numeric = std::integral<T> || std::floating_point<T>;
9+
10+
// ① 函数模板
11+
template <Numeric T> T square(T x) {
12+
return x * x;
13+
}
14+
15+
// ② 类模板:只对数值类型实例化
16+
template <Numeric T> struct SafeNumber {
17+
T value;
18+
SafeNumber(T v) : value(v) {}
19+
// ③ 成员函数也能再加自己的约束
20+
SafeNumber& operator+=(Numeric auto other) {
21+
value += other;
22+
return *this;
23+
}
24+
};
25+
26+
// ④ 简写语法:约束直接写在 auto 前
27+
Numeric auto half(Numeric auto x) {
28+
return x / 2;
29+
}
30+
31+
int main() {
32+
std::cout << "square(4) = " << square(4) << "\n";
33+
std::cout << "square(2.5) = " << square(2.5) << "\n";
34+
SafeNumber sn(3);
35+
sn += 4;
36+
std::cout << "SafeNumber(3) + 4 = " << sn.value << "\n";
37+
std::cout << "half(10) = " << half(10) << "\n";
38+
return 0;
39+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// enable_if vs concept 的报错对比:同一个 add,两种约束方式,故意传错类型看报错差异
2+
// 对应文章:documents/vol4-advanced/vol3-metaprogramming-cpp20-23/01-concepts.md
3+
//
4+
// 默认编译(演示两个 add 都能正常工作):
5+
// g++ -Wall -Wextra -std=c++20 enable_if_vs_concept.cpp -o eic && ./eic
6+
//
7+
// 复现报错对比(分别加宏编译,看 concept 报错如何直接点名约束、enable_if 版暴露 SFINAE 内部):
8+
// g++ -std=c++20 -DDEMO_OLD enable_if_vs_concept.cpp # enable_if 版报错
9+
// g++ -std=c++20 -DDEMO_NEW enable_if_vs_concept.cpp # concept 版报错(constraints not
10+
// satisfied)
11+
#include <concepts>
12+
#include <iostream>
13+
#include <string>
14+
#include <type_traits>
15+
16+
namespace old_way {
17+
// C++17 的 enable_if:把约束塞进额外的默认模板参数
18+
template <typename T,
19+
typename = std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>>>
20+
T add(T a, T b) {
21+
return a + b;
22+
}
23+
} // namespace old_way
24+
25+
namespace new_way {
26+
// C++20 concept:约束写进签名,有名字、可复用
27+
template <typename T>
28+
concept Numeric = std::integral<T> || std::floating_point<T>;
29+
30+
template <typename T>
31+
requires Numeric<T>
32+
T add(T a, T b) {
33+
return a + b;
34+
}
35+
} // namespace new_way
36+
37+
int main() {
38+
std::cout << "old_way::add(2, 3) = " << old_way::add(2, 3) << "\n";
39+
std::cout << "new_way::add(2, 3) = " << new_way::add(2, 3) << "\n";
40+
41+
#if DEMO_OLD
42+
std::string s1 = "a", s2 = "b";
43+
old_way::add(s1, s2); // 故意传错:看 enable_if 版报错(no type named 'type' in enable_if<false>)
44+
#elif DEMO_NEW
45+
std::string s1 = "a", s2 = "b";
46+
new_way::add(s1, s2); // 故意传错:看 concept 版报错(constraints not satisfied -> Numeric<T>)
47+
#endif
48+
return 0;
49+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Requires 表达式四种成分(简单/类型/复合/嵌套)+ 当 bool 用 + 用表达式定义 concept
2+
// 对应文章:documents/vol4-advanced/vol3-metaprogramming-cpp20-23/03-requires-expressions.md
3+
// 编译运行:g++ -Wall -Wextra -std=c++20 requires_expression.cpp -o req && ./req
4+
#include <concepts>
5+
#include <iostream>
6+
#include <vector>
7+
8+
// 四种成分一次用全:简单要求、类型要求、复合要求、嵌套要求
9+
template <typename T>
10+
concept Container = requires(T t) {
11+
t.begin(); // ① 简单要求
12+
t.end();
13+
typename T::value_type; // ② 类型要求
14+
{ t.size() } -> std::convertible_to<std::size_t>; // ③ 复合要求
15+
requires std::integral<typename T::value_type>; // ④ 嵌套要求
16+
};
17+
18+
static_assert(Container<std::vector<int>>); // value_type=int,integral 通过
19+
static_assert(!Container<int>); // int 没有 begin/end
20+
21+
// requires 表达式当 bool 用:if constexpr 里直接判断
22+
template <typename T> void process(T t) {
23+
(void)t;
24+
if constexpr (requires(T x) { x.empty(); }) {
25+
std::cout << "has empty()\n";
26+
} else {
27+
std::cout << "no empty()\n";
28+
}
29+
}
30+
31+
int main() {
32+
process(std::vector<int>{}); // vector 有 empty()
33+
process(42); // int 没有
34+
return 0;
35+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// same_as 的两个坑:same_as<int,const int> 为 false(cv 限定让它们成为不同类型);concept 当 is_same
2+
// 误用 对应文章:01-concepts.md、02-constraining-templates.md 编译运行:g++ -Wall -Wextra -std=c++20
3+
// same_as_pitfall.cpp -o sas && ./sas
4+
#include <concepts>
5+
#include <iostream>
6+
#include <type_traits>
7+
8+
// 坑二:concept 当 is_same 用,把 T 锁死成 int
9+
// 能跑,但这种「锁死成具体类型」通常不如直接写非模板函数 void only_int(int) 清楚
10+
template <std::same_as<int> T> void only_int(T x) {
11+
std::cout << "got int: " << x << "\n";
12+
}
13+
// same_as 真正的用武之地是约束两个参数的关系:
14+
// template <typename A, typename B> requires std::same_as<A, B>
15+
16+
int main() {
17+
std::cout << std::boolalpha;
18+
19+
// 坑一:const 限定让 int 和 const int 成为不同类型,same_as 返回 false
20+
std::cout << "same_as<int, const int>: "
21+
<< std::same_as<int, const int> << "\n"; // false
22+
// 想判断「剥掉 cv/引用后是否相同」,先用 remove_cvref_t
23+
std::cout << "same_as<int, remove_cvref_t<const int>>: "
24+
<< std::same_as<int, std::remove_cvref_t<const int>> << "\n"; // true
25+
26+
// 坑二演示:only_int 只收 int
27+
only_int(42);
28+
// only_int(3.14); // 编译失败:double 不满足 same_as<int>
29+
return 0;
30+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// 标准库 <concepts> 常用概念实测:same_as / convertible_to / derived_from / common_with / integral /
2+
// floating_point 对应文章:documents/vol4-advanced/vol3-metaprogramming-cpp20-23/01-concepts.md
3+
// 编译运行:g++ -Wall -Wextra -std=c++20 stdconcepts_demo.cpp -o stdc && ./stdc
4+
#include <concepts>
5+
#include <iostream>
6+
#include <vector>
7+
8+
struct Base {};
9+
struct Derived : Base {};
10+
struct Unrelated {};
11+
12+
int main() {
13+
std::cout << std::boolalpha;
14+
std::cout << "same_as<int,int>: " << std::same_as<int, int> << "\n";
15+
std::cout << "same_as<int, const int>: " << std::same_as<int, const int> << "\n";
16+
std::cout << "convertible_to<int,double>: " << std::convertible_to<int, double> << "\n";
17+
std::cout << "convertible_to<double,int>: " << std::convertible_to<double, int> << "\n";
18+
std::cout << "derived_from<Derived,Base>: " << std::derived_from<Derived, Base> << "\n";
19+
std::cout << "derived_from<Unrelated,Base>: " << std::derived_from<Unrelated, Base> << "\n";
20+
std::cout << "common_with<int,double>: " << std::common_with<int, double> << "\n";
21+
std::cout << "default_initializable<int>: " << std::default_initializable<int> << "\n";
22+
std::cout << "integral<int>: " << std::integral<int> << "\n";
23+
std::cout << "integral<bool>: " << std::integral<bool> << "\n";
24+
std::cout << "floating_point<float>: " << std::floating_point<float> << "\n";
25+
return 0;
26+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Subsumption(约束蕴含):多个带 concept 约束的重载,编译器靠约束的包含关系选最特定的
2+
// 对应文章:documents/vol4-advanced/vol3-metaprogramming-cpp20-23/02-constraining-templates.md
3+
// 编译运行:g++ -Wall -Wextra -std=c++20 subsumption_overloads.cpp -o sub && ./sub
4+
#include <iostream>
5+
6+
// 第一组:Dog 蕴含 Animal(Dog = Animal<T> && 额外要求),subsumption 让更窄的重载胜出
7+
template <typename T>
8+
concept Animal = requires(T t) { t.eat(); };
9+
template <typename T>
10+
concept Dog = Animal<T> && requires(T t) { t.bark(); };
11+
12+
void describe(Animal auto const&) {
13+
std::cout << "an animal\n";
14+
}
15+
void describe(Dog auto const&) {
16+
std::cout << "a dog\n";
17+
}
18+
19+
// 第二组:&& 组合产生 {A<T>, B<T>} 原子约束集合,C 同时 subsumes A 和 B
20+
template <typename T>
21+
concept A = requires(T t) { t.a(); };
22+
template <typename T>
23+
concept B = requires(T t) { t.b(); };
24+
template <typename T>
25+
concept C = A<T> && B<T>;
26+
27+
void f(A auto const&) {
28+
std::cout << "A\n";
29+
}
30+
void f(B auto const&) {
31+
std::cout << "B\n";
32+
}
33+
void f(C auto const&) {
34+
std::cout << "C\n";
35+
}
36+
37+
struct Cat {
38+
void eat() {}
39+
};
40+
struct Pup {
41+
void eat() {}
42+
void bark() {}
43+
};
44+
struct Both {
45+
void a() {}
46+
void b() {}
47+
};
48+
49+
int main() {
50+
describe(Cat{}); // 只满足 Animal -> 宽重载
51+
describe(Pup{}); // 满足 Dog -> 窄重载(subsumes 宽重载)
52+
f(Both{}); // 同时满足 A、B、C -> C(subsumes A 和 B)
53+
return 0;
54+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// requires 表达式不求值(unevaluated):里面的调用只检查能否编译,根本不执行、无副作用
2+
// 对应文章:documents/vol4-advanced/vol3-metaprogramming-cpp20-23/03-requires-expressions.md
3+
// 编译运行:g++ -Wall -Wextra -std=c++20 unevaluated.cpp -o ue && ./ue
4+
// 预期:concept 求值时 counter 仍为 0,只有 main 里真正调用 increment 才变 1
5+
#include <iostream>
6+
7+
int counter = 0;
8+
int increment() {
9+
++counter;
10+
std::cout << "[副作用] increment 被调用了\n";
11+
return 1;
12+
}
13+
14+
template <typename T>
15+
concept MentionsIncrement = requires(T t) {
16+
increment(); // 只检查「这个调用合不合法」,不求值、不执行
17+
};
18+
19+
int main() {
20+
static_assert(MentionsIncrement<int>); // 满足:increment() 调用合法
21+
std::cout << "concept 求值完毕,counter = " << counter << "\n";
22+
increment(); // 这里才真正调用
23+
std::cout << "真正调用后,counter = " << counter << "\n";
24+
return 0;
25+
}

0 commit comments

Comments
 (0)