C++程序员应了解的那些事(81) concept for C++20
concept其实已经不是什么新东西了,属于C++里面喊了无数次狼来了的东东,直到C++20才正式进入。其实也难怪,毕竟C++是一个非常庞大的机器,牵一发而动全身,需要将各种边边角角的情况都考虑严谨了,再加入标准才更靠谱。
concept对于普通开发者来说,最明显的作用就是在模板参数不满足鸭子类型的约束时编译器不再给出几千行奇奇怪怪的错误。当然还有其它的作用,比如说concepts可以用来实现函数的重载、新的concepts可以基于已有的concepts定义从而进行扩展等等。
标准库已经定义了一些常用的concepts了,位于头文件<concepts>中。concept的使用和定义都很简单,本篇只讲使用,为了讲后面关于使用的例子先定义一个最简单的cocept。
template<typename T>
concept Integral = std::is_integral<T>::value;
C++20引入了concept和requires关键字,相关功能的使用方法至少有四种,似乎很多,但是只要看一遍即使记不住下次看到别人写的也能看懂。我们用上面定义好的Integral来定义一个最简单的Add函数,以这个函数为例来说明各种不同的用法格式。
第一种是Bjarne Stroustrup最喜欢的,他甚至觉得剩下的几种用法都是多余的,但是众口难调,投票决定,很多东西他也做不了主。来看看这种用法吧,就是用了一个多余的auto关键字,来说明auto前面的Integral是一个concept:
Integral auto Add(Integral auto a, Integral auto b)
{
return a + b;
}
// 可以用新的格式:Integral auto c = Add(10, 3)来调用,
// 当然也可以用以前的调用方法:int c = Add(10, 3)
再来看第二种,在template声明以后紧接着用requires关键字说明模板参数需要满足的concept:
template<typename T>
requires Integral<T>
T Add(T a, T b)
{
return a + b;
}
// 这种用法不必对函数声明中的每个T都写成长长的Integral auto,
// 当函数参数较多时明显比第一种要好,所以Bjarne Stroustrup也不见得是对的,毕竟人都有偏见。
继续看第三种,这种和第二种的区别就是将requires Integral<T>挪动了位置,放在了函数声明的后面,效果是一样的:
template<typename T>
T Add(T a, T b) requires Integral<T>
{
return a + b;
}
第四种是省略了requires关键字,直接将concept的名字放入到模板参数前面,同时也省去了平常模板声明用的typename或class,这种方法最直观,如下:
template<Integral T>
T Add(T a, T b)
{
return a + b;
}
其实以前在concept-ts中还有第五种比较奇葩的语法,貌似在C++20中已经删掉了,但是在gcc中仍然可以编译通过(毕竟gcc是第一个实现concept-ts的编译器),如下:
Integral{T}
T Add(T a, T b)
{
return a + b;
}
因为上面已经指定了Add函数必须用Inetegral的参数才能调用,用其它的类型就会编译出错,编译器应该会给出明确的错误。用double类型的参数来调用一下看看:
auto c = Add(10.3, 5.2);
// 编译器给出类似这样的错误:
// error: use of function 'T Add(T, T) [with T = double]' with unsatisfied constraints
concept还可用于函数的重载,假设我再加上一个普通的Add函数,上面的代码可以编译通过,普通的Add函数就和C++20之前的函数一样:
template<class T>
T Add(T a, T b)
{
return a + b;
}
加了这个 函数以后,不满足Integral的参数就会调用这个函数,而满足Integral的参数会调用之前用concept定义的函数,编译器一如既往地选择最匹配的函数。
参考:
推荐阅读
-
程序员应了解的那些事(16)C语言中利用setjmp和longjmp做异常处理 / 不要在C++中使用setjmp和longjmp
-
C++程序员应了解的那些事(64)~ 指向 Data Member 的指针 <成员指针>
-
C++程序员应了解的那些事(68)非类型模板参数
-
C++程序员应了解的那些事(47)函数之 传入传出参数 / 默认参数
-
C++程序员应了解的那些事(36)Effective STL第6条:当心C++编译器中最烦人的分析机制 --- 调用构造函数被误认为是函数声明的问题
-
C++程序员应了解的那些事(63)STL内建函数对象、仿函数
-
C++程序员应了解的那些事(62)~ list::splice()函数详解
-
C++程序员应了解的那些事(94)之STL容器内存释放问题
-
C++程序员应了解的那些事(99)之 C++中的ODR法则
-
程序员应了解的那些事(21)do{...}while(0)的意义