C++20(概念和约束)

声明:本章采纳 https://cppreference.com/ 和 MSVC 来演示。

目录

[一 什么是概念](#一 什么是概念)

[1. 官方文档介绍:](#1. 官方文档介绍:)

[二 概念的定义](#二 概念的定义)

[1 自定义概念的定义](#1 自定义概念的定义)

[2. 库内置的概念](#2. 库内置的概念)

[三 requires子句和表达式](#三 requires子句和表达式)

[3.1 requires 子句](#3.1 requires 子句)

[3.2 requries 表达式](#3.2 requries 表达式)

[四 约束](#四 约束)

[1. 约束的 4 种类型](#1. 约束的 4 种类型)

[2. 约束的偏序规则](#2. 约束的偏序规则)


一 什么是概念

1. 官方文档介绍:

  • 类模板、函数模板(包括泛型lambda)和其他模板化函数(通常是类模板的成员)可能与一个约束相关联,该约束指定了对模板参数的要求,可用于选择最合适的函数重载和模板特化。
  • 这种需求的命名集合称为概念。每个概念都是一个谓词,在编译时计算,并成为模板接口的一部分,用作约束:

说人话,就是给模板添加一种约束,让不符合的条件的错误信息更直观看懂,且编码方式统一。

C++20 之前限制模板:

  • SFINAE:decltype,std::enable_if,if constexpr
  • 类型萃取:模板特化

等....

这些方式千奇百怪,有了概念之后遵守统一的语法格式更方便编码和调试。

模板有一个缺点就是报错信息很难看懂,并且只有实例化的时候才会报错,浪费的编译时间,且报错信息难看懂,有了概念,就不需要在编译时识别,更快报错信息更容易看懂。

举个例子:

cpp 复制代码
std::list<int> l = {3, -1, 10};
std::sort(l.begin(), l.end()); 
// Typical compiler diagnostic without concepts:
// invalid operands to binary expression ('std::_List_iterator<int>' and
// 'std::_List_iterator<int>')
//                           std::__lg(__last - __first) * 2);
//                                     ~~~~~~ ^ ~~~~~~~
// ... 50 lines of output ...
//
// Typical compiler diagnostic with concepts:
// error: cannot call std::sort with std::_List_iterator<int>
// note:  concept RandomAccessIterator<std::_List_iterator<int>> was not satisfied

这段代码使用了算法库的 sort,其参数必须为随机访问迭代器,当传入 list 的时候,实例化之后,std::__lg(__last - __first) * 2 报错,该代码表示对整个容器的大小取个 log ,也就是定义他的最高的递归层数,防止栈溢出,这里报错因为 list 不是随机访问迭代器,不支持 - 所以报错,这对于不熟悉 sort 的底层实现的读者看到这样的报错难以理解。

二 概念的定义

1 自定义概念的定义

cpp 复制代码
template < template-parameter-list >
concept concept-name attr (optional) = constraint-expression;

concept 定义概念的关键字

name 可学可不写,不写就是匿名的概念

constraint - expression 返回值为 bool 的常量表达式

1.1 一个最简单的概念

cpp 复制代码
template <class T>
concept name = true;

template <name T>
void fun(T t){}

fun("hello world"  /* 任意传值 */);

概念名可以作为后面所有模板的类型声明,根据传入的类型来判断是否符合定义的概念,即定义的概念右操作数结果是否为 true。

1.2 概念右操作数的写法

concept name = 自定义的常量表达式(自己写的)/库内置的表达式/require 子句/表达式等....

示例:

cpp 复制代码
template <class T>
// T 是否小于等于 4字节
concept name1 = sizeof(T) <= 4;

template <class T>
// 是否为 int
concept name2 = std::is_integral_v<T>;

//
template <class T>
concept name3 = requires(T val)
{
	// 能被转换成 string
	std::to_string(val);
};

2. 库内置的概念

cpp 复制代码
_EXPORT_STD template <class _Ty>
concept integral = is_integral_v<_Ty>;

_EXPORT_STD template <class _Ty>
concept signed_integral = integral<_Ty> && static_cast<_Ty>(-1) < static_cast<_Ty>(0);

_EXPORT_STD template <class _Ty>
concept unsigned_integral = integral<_Ty> && !signed_integral<_Ty>;

_EXPORT_STD template <class _Ty>
concept floating_point = is_floating_point_v<_Ty>;

库里的概念本质是拿 <type_traits> 来定义了概念,这些概念可以用作于 requires 子句/表达式内,并且可以不用传类型。

示例:

cpp 复制代码
template <class T>
concept name4 = requires(T val)
{
	{ val } -> std::integral<>;    // 是否为 int
	{ val } -> std::same_as<int>;  // 是否和 int 相等
};

val 作为 std::integral 的第一个参数传递。

并且库里的概念和自定义的概念可以直接作为模板的声明

示例:

cpp 复制代码
template <std::integral T>
void fun(T t){}

三 requires子句和表达式

前面概念的定义的名称可以省略,本质是借助 requries 来实现的。

3.1 requires 子句

示例:requires 子句

cpp 复制代码
// 可以跟在模板后面
template <std::integral T> requires true && std::integral<T>;
void fun1(T t){}

// 可以跟在函数后面
template <std::integral T>
void fun2(T t) requires true {}

// 可以跟 && || 运算符

3.2 requries 表达式

前面的 requires 子句用来定义匿名的概念,而 requires 表达式则是用来定义有名的概念

示例:

cpp 复制代码
template <class T>
concept name5 = requires(T a,T b)
{
	a + b;                                    // 是否能 +
    a.size();                                 // 是否有 size 方法
    typename a::iterator;                     // 是否有内嵌迭代器
    { a + b } noexpect -> std::same_as<int>   // 表达式是否不会抛异常 && 是否是 int

    requires std::integral<T>;       // 内嵌 requires 如果为 false 整个 requires 为false
    std::integral<T>                 // 和上面不一样,该表达式结果不影响整个 requires 的结果

    requires requires(T val)           // 套娃
    {
	    std::to_string(val);           
    };

    requires requires(T val)           // 套娃
		{
			std::to_string(val);
		} 
		&&  
		requires(T val)
		{
			std::to_string(val);
		};
    // 上面 2 种写法:requires 后面又跟一个 requires ,requires 表达式会被转换成 bool
    // 又因为 requires 后面必须跟一个常量表达式,所以符合语法要求
};

requires 表达式函数体内至少要有一个约束条件,参数可选。

可以看出来 requires 表达式的写法有非常多种。

四 约束

上面所说的所有情况都可以说是一种约束,不管是库提供的还是自定义的概念或者表达式都可以算是一种约束。

1. 约束的 4 种类型

原子约束:一个返回值为 bool 的常量表达式,为一个整体,不可分割,如前面写的

concept name = std::integral<T>;

合取:多个原子约束之间带 &&,如:

concept name = std::integral<T> && std::same_as<T,T>;

析取:多个原子约束之间带 ||,如:

concept name = std::integral<T> || std::same_as<T,T>;

折叠扩展约束:传入类型参数包对他们进行约束,如:

cpp 复制代码
template <class T>
concept name1 = sizeof(T) <= 4;

template <class ...T> requires (name1<T> && ...)
void fun6() {}

2. 约束的偏序规则

如果有多个匹配的版本,则会优先匹配最严格的版本,如:

cpp 复制代码
template <class T>
concept tt1 = std::integral<T>;

template <class T>
concept tt2 = std::integral<T> && requires(T val) { val++; };

template <tt1 T>
void fun7() { std::cout << "tt1" << std::endl; }

template <tt2 T>
void fun7() { std::cout << "tt2" << std::endl; }

fun7<int>();

tt1虽然匹配,但 tt2 包含了 tt1 也更严格

相关推荐
訫悦4 天前
体验在Qt中简单使用C++20的协程
qt·c++20·协程
fpcc8 天前
C++20中的预处理器宏——__VA_OPT__
c++20
Codeking__10 天前
C++20的consteval和constinit(接C++11的constexpr)
算法·c++20
六bring个六13 天前
C++20协程
c++20·协程
C++实习生13 天前
Visual C++ 2005 Express 中文版
express·c++20
Ethan Wilson15 天前
VS2019 C++20 模块相关 C1001: 内部编译器错误
开发语言·c++·c++20
DYS_房东的猫15 天前
《 C++ 零基础入门教程》第10章:C++20 核心特性 —— 编写更现代、更优雅的 C++
java·c++·c++20
ice_junjun25 天前
C++20 线程返回值处理指南
c++20·c++ 多线程返回值
凌乱风雨12111 个月前
从源码角度解析C++20新特性如何简化线程超时取消
前端·算法·c++20