C++20 Concepts 极简介绍
Concepts 是 C++20 的重大功能之一,主要目的是对模版参数进行约束,从而得到如下好处:
提高代码的可读性(替代如同密码一般的 SFINAE 技术)
增加编译速度
同时还可以提供更好的编译报错(模版编程最恼人的问题之一就是如同天书的编译器报错)
定义 concepts
C++20 针对 concepts 引入了两个新的关键字(requires
,concept
),同时在标准库中预定义了一组 concepts。
一个最简单的 concept 声明如下
上边的代码定义了 integral
concept,其中 is_integral_v
来自于 C++11/14 的 type trait。
另一种定义 concept 的方法是使用 require
关键字
requires
关键字可以支持更为灵活的 concept 语法,上边的代码的意思是:
类型
T
具有print
成员函数(1)类型
T
具有toString
成员函数,并且成员函数的返回值为std::sring
(2)。
使用 concepts 约束模版参数
上边是 C++98 就支持的模版语法,log
函数接受任意类型的参数,但是要求该参数具有 print
成员函数。如果类型 T
没有 print
成员函数,编译器就会报错,此时的错误信息会非常晦涩难懂。
使用 concepts 可以改善这种情况
新加的 requires
语句可以约束类型 T
,当类型 T
没有 print
成员函数的时候,编译器的报错一般是指出类型不满足 printable
约束。
针对不同类型使用不同的函数实现
在 C++98 时代,要针对不同的类型使用不同的函数实现,通常会使用函数重载:
以上边的 same_number
为例,就算是只处理数字类型,上边两个函数也可能不够,因为 C++ 还有 unsigned int
,long
, int_64
, float
等等很多的数字类型。
为了处理很多类型,我们会使用模版编程:
现在我们的代码可以处理所有的数字类型,但是有一个问题,数字的比较都是通过 ==
符号来实现,在某些情况下,我们在比较浮点数的时候更希望的是两个数之间的差值小于某个很小的数字就认为他们相等,这时候我们要么回到函数重载的方式,要么就得使用非常晦涩难懂的 SFINAE 技术。
使用 concept 可以很好的解决这个问题:
在(1)和(2)的地方我们分别定义了针对整数和浮点数的 concept,在(3)和(4)的模版函数中我们使用 requires 来分别指定不同的 concept,函数体使用不同实现。
看起来非常像函数重载,只不过这一次,我们是针对一系列的类型进行的重载。
简化代码
之前的代码,我们使用了 concept 的完整形式,在语法上 C++ 支持进一步的简化,same_number
可以简写为下边的形式:
这看起来就像把 concept 当成了类型声明一样,只不过它是作用于类型的类型。
得益于 C++20 新的模版函数的写法,我们可以去掉 template 进一步简化为:
需要注意的是,这种写法和原始代码不是严格等价,原始代码中要求参数 l
和 r
是相同类型,而现在的写法中并没有这个要求,只要求参数满足 concept 就行,比如 int
和 unsigned int
都满足 integral_number
concept,这时候给 same_number
的 l
参数传入 int
类型,r
参数传入 unsigned int
类型符合语法,但是当对 int
和 unsigned int
类型进行比较操作时,就可能会出现微妙的错误。
Concepts 是一个很大的功能,本文只是简单的做了一个介绍,我们以后再详细介绍其他更高级的用法。
版权声明: 本文为 InfoQ 作者【董一凡】的原创文章。
原文链接:【http://xie.infoq.cn/article/1868cd56465b41ec47dfcdf23】。文章转载请联系作者。
评论