写点什么

C++20 Concepts 极简介绍

用户头像
董一凡
关注
发布于: 1 小时前
C++20 Concepts 极简介绍

Concepts 是 C++20 的重大功能之一,主要目的是对模版参数进行约束,从而得到如下好处:


  • 提高代码的可读性(替代如同密码一般的 SFINAE 技术)

  • 增加编译速度

  • 同时还可以提供更好的编译报错(模版编程最恼人的问题之一就是如同天书的编译器报错)

定义 concepts

C++20 针对 concepts 引入了两个新的关键字(requiresconcept),同时在标准库中预定义了一组 concepts。


一个最简单的 concept 声明如下


template <typename T>concept integral = std::is_integral_v<T>;
复制代码


上边的代码定义了 integral concept,其中 is_integral_v 来自于 C++11/14 的 type trait。


另一种定义 concept 的方法是使用 require 关键字


template <typename T>concept printable = requires(T t) {  t.print(); //1  {t.toString()} -> std::same_as<std::string>; //2};
复制代码


requires 关键字可以支持更为灵活的 concept 语法,上边的代码的意思是:


  • 类型 T 具有 print 成员函数(1)

  • 类型 T 具有 toString 成员函数,并且成员函数的返回值为 std::sring (2)。

使用 concepts 约束模版参数

template <typename T>void log(T t) {  t.print();}
复制代码


上边是 C++98 就支持的模版语法,log 函数接受任意类型的参数,但是要求该参数具有 print 成员函数。如果类型 T 没有 print 成员函数,编译器就会报错,此时的错误信息会非常晦涩难懂。


使用 concepts 可以改善这种情况


template <typename T>requires printable<T>void log(T t) {  t.print();}
复制代码


新加的 requires 语句可以约束类型 T ,当类型 T 没有 print 成员函数的时候,编译器的报错一般是指出类型不满足 printable 约束。

针对不同类型使用不同的函数实现

在 C++98 时代,要针对不同的类型使用不同的函数实现,通常会使用函数重载:


bool same_number(int l, int r) {  return l == r;}bool same_number(double l, double r) {  return l == r;}
复制代码


以上边的 same_number 为例,就算是只处理数字类型,上边两个函数也可能不够,因为 C++ 还有 unsigned intlongint_64float 等等很多的数字类型。


为了处理很多类型,我们会使用模版编程:


template <typename T>bool same_number(T l, T r) {  return l == r;}
复制代码


现在我们的代码可以处理所有的数字类型,但是有一个问题,数字的比较都是通过 == 符号来实现,在某些情况下,我们在比较浮点数的时候更希望的是两个数之间的差值小于某个很小的数字就认为他们相等,这时候我们要么回到函数重载的方式,要么就得使用非常晦涩难懂的 SFINAE 技术。


使用 concept 可以很好的解决这个问题:


template <typename T>concept integral_number = std::is_integral_v<T>; //1
template <typename T>concept floating_number = std::is_floating_point_v<T>; //2
template <typename T>requires integral_number<T> //3bool same_number(T l, T r) { return l == r;}
template <typename T>requires floating_number<T> //4bool same_number(T l, T r) { return std::abs(l - r) < T(0.000001);}
int main() { assert(same_number(1, 1)); assert(same_number(1.0, 1.000000001)); return 0;}
复制代码


在(1)和(2)的地方我们分别定义了针对整数和浮点数的 concept,在(3)和(4)的模版函数中我们使用 requires 来分别指定不同的 concept,函数体使用不同实现。


看起来非常像函数重载,只不过这一次,我们是针对一系列的类型进行的重载。

简化代码

之前的代码,我们使用了 concept 的完整形式,在语法上 C++ 支持进一步的简化,same_number 可以简写为下边的形式:


template <integral_number T>bool same_number(T l, T r) {  return l == r;}
template <floating_number T>bool same_number(T l, T r) { return std::abs(l - r) < T(0.000001);}
复制代码


这看起来就像把 concept 当成了类型声明一样,只不过它是作用于类型的类型。


得益于 C++20 新的模版函数的写法,我们可以去掉 template 进一步简化为:


bool same_number(integral_number auto l, integral_number auto r) {  return l == r;}
bool same_number(floating_number auto l, floating_number auto r) { return std::abs(l - r) < decltype(l)(0.000001);}
复制代码


需要注意的是,这种写法和原始代码不是严格等价,原始代码中要求参数 lr 是相同类型,而现在的写法中并没有这个要求,只要求参数满足 concept 就行,比如 intunsigned int 都满足 integral_number concept,这时候给 same_numberl 参数传入 int 类型,r 参数传入 unsigned int 类型符合语法,但是当对 intunsigned int 类型进行比较操作时,就可能会出现微妙的错误。


Concepts 是一个很大的功能,本文只是简单的做了一个介绍,我们以后再详细介绍其他更高级的用法。

发布于: 1 小时前阅读数: 3
用户头像

董一凡

关注

写了14年程序,工作之余写作和画画。 2017.12.04 加入

十多年程序员生涯,写了八年移动客户端,后来也参与过一定量的 web 前端后端的开发。现在基本上啥代码都会写一点。近期比较迷茫,不知道接下来的职业道路怎么走。开发之外,也靠画画,写作来自我娱乐一下。

评论

发布
暂无评论
C++20 Concepts 极简介绍