本文记录C++20新特性之概念(Concepts)。
文章目录
- [第一章 C++20核心语法特性](#第一章 C++20核心语法特性)
-
- [1.1 概念 (Concepts)](#1.1 概念 (Concepts))
-
- [1.1.1 Concepts实现原理](#1.1.1 Concepts实现原理)
- [1.1.2 concepts的使用举例](#1.1.2 concepts的使用举例)
-
- [示例1 指定参数类型](#示例1 指定参数类型)
- 示例2:约束成员函数
- [示例3:基于 Concept 的函数重载](#示例3:基于 Concept 的函数重载)
- [1.1.3 总结](#1.1.3 总结)
第一章 C++20核心语法特性
1.1 概念 (Concepts)
概念是C++20引入的新特性,用于模板编程中,被认为是C++11引入auto和lambda以来,模板编程中的最大变革。
C++20之前,模板编程(泛型编程)虽然强大,但存在两个著名的痛点:
缺点1: 不能精确定位错误位置:当向模板中传递一个不支持的类型时,编译器出现报错,但是不能精确指向哪一行,并支持错误。
缺点2:对预定类型的约束。在泛型编程时,想要指定某个函数模板T只能支持整型 或者 参数T必须有成员函数 hash(),通常使用SFINAE特性或enable_if来实现约束,这实现起来比较麻烦,请看《C++模板与泛型编程》笔记。
C++20引入了Concepts,就是为了给模板参数加上语义约束,如果参数传递错误,编译器给出清晰的错误提示,可轻松解决上面两个问题。
1.1.1 Concepts实现原理
concepts 本质上是编译期的谓词,用来约束类型,如果类型满足则返回true, 否则返回false.
语法:
concept: 定义概念
requires : 在模板中使用概念进行约束。
concepts的两个主要作用:
1 约束模板参数:明确指定模板参数必须满足的条件(如必须是整数、必须可排序)。
2 改善错误信息:如果约束不满足,编译器提示"类型不满足"这样的错误。
1.1.2 concepts的使用举例
示例1 指定参数类型
编写一个函数,必须接收int或浮点数类型的参数,不接收其他类型,实现如下:
cpp
// 1. 定义一个 Concept
// T 必须是整数或浮点数
template <typename T>
concept Number = std::integral<T> || std::floating_point<T>;
// 2. 使用 Concept 约束函数参数
// 方式1:使用requires 关键字
template<typename T>
requires Number<T>
void add(T a, T b)
{
std::cout << "Sum: " << (a + b) << std::endl;
}
// 写法B : 发直接替换typename 简洁
void sub(Number auto a, Number auto b)
{
std::cout << "Difference: " << (a - b) << std::endl;
}
void test()
{
add(1, 2);
sub(1, 2);
// 传入字符串
// add("aa", "bb");
// "sp28::add": 未找到匹配的重载函数
}
示例2:约束成员函数
在写模板编程时,要求类型T必须有一个hash()函数,且返回值为size_t.
cpp
// 假设要求类型T必须有一个hash()函数,且返回值为size_t
template<typename T>
concept Hashable = requires(T a)
{
{ a.hash() } -> std::same_as<size_t>;
// 表达式 a.hash() 必须合法,且返回值必须能转换为 size_t
};
struct MyData
{
size_t hash() const
{
return 42; // 示例实现
}
};
// 对象2
struct BadData
{
int value;
// 没有 hash() 方法
void hash()
{
cout << "没有返回值的hash()" << endl;
}
};
// 使用concept 约束函数
void process(Hashable auto const& item)
{
std::cout << "Hash " << item.hash() << std::endl;
}
void test()
{
MyData d;
process(d); // 合法
// Hash 42
BadData bd;
//process(bd); // 编译错误,BadData 不满足 Hashable
// 编译错误:"process": 未找到匹配的重载函数
}
示例3:基于 Concept 的函数重载
指定某个重载版本的类型T必须支持随机访问。
cpp
// 版本1: 支持任何标准容器
template<typename T>
void print(const T& t)
{
std::cout << "支持任意类型打印" << endl;
}
// 版本2:专门指针 可随机访问的容器
template<typename T>
requires std::ranges::random_access_range<T>
void print(const T& t)
{
cout << "支持随机访问容器打印" << endl;
}
void test()
{
std::list<int> lst = { 1,2,3 };
std::vector<int> vec = { 4,5,6 };
print(lst); // 调用版本1
// 支持任意类型打印
print(vec); // 调用版本2
/*
支持随机访问容器打印
*/
}
1.1.3 总结
"concepts" 引入让模板代码更简介、更容易调试。以后写泛型编程时,尽量使用Concept进行约束。
std::ranges::random_access_range 也是C++20引入的新特性,见下。