概述
众所周知,C++的泛型强大,很多语言都学了,比如C#、Java,虽然Java的泛型只是语法糖,不值得一提。
C++的template实际上是创造了一种新的语言,这个语言是编译期的,相当于编译器把代码解释成C++语言,然后再编译成机器指令。STL的诞生,是颠覆性的创举,然而STL所用到的泛型还只是C++template中其中一部分特性。
C++使用template实现泛型算法,我们来看下一个简单的例子。
template<typename T>
T inc(T a)
{
return ++a;
}
这个简单几行代码,就实现了适合所有C++内建类型的自增函数,甚至只要class支持加法++运算的重载,也都是支持的。
设计一个原则,先宽后严,其实非常困难的。对于C++也不例外。上面的代码,对于T类型的要求,只是概念层面,无法通过函数的声明能够直接看出T的限制。只有用户写完代码,一看才知道有编译错误,而且糟糕的是甚至没有编译错误,真的会怼天怼地。比如:
using namespace std;
template<typename T>
T inc(T a)
{
return ++a;
}
int main(int argc, const char * argv[])
{
int a = 0;
cout << inc(&a) << endl;
return 0;
}
上面代码inc函数参数a不能传递指针,编译的时候没有错误提示,只有到运行的时候可能会出问题。
另外,template的编译错误提示又做得不好,这也是C++最为诟病的地方。于是concept特性一直呼声比较高。
concept
concept一个用处,就是对T进行限制,让编译器自动检查T是否符合函数的要求。
我们先看下,不用concept,如何判断T是否符合要求:
template<typename T>
T inc(T a)
{
static_assert(std::is_integral<T>::value);
return ++a ;
}
int main(int argc, const char * argv[])
{
int a = 0;
cout << inc(&a) << endl;
return 0;
}
这个时候,编译器就会编译错误了,inc函数不能传递指针,T只有满足是整型才可以。虽然可以解决问题,但是T的检查是在函数内实现的,只有出错了,用户才知道。不然从函数inc的声明直观看出T的要求。
我们看看使用concept如何实现的。代码如下:
template<typename T>
concept Integral = std::is_integral<T>::value;
template<Integral T>
T inc(T a)
{
return ++a ;
}
int main(int argc, const char * argv[])
{
int a = 0;
cout << inc(&a) << endl;
return 0;
}
这个实现,使用concept关键字。如果我们预期,编译不通过,我们在看看编译错误:
main.cpp:40:3: note: constraints not satisfied
main.cpp: In instantiation of ‘T inc(T) [with T = int*]’:
main.cpp:48:19: required from here
main.cpp:37:9: required for the satisfaction of ‘Integral<T>’ [with T = int*]
main.cpp:37:41: note: the expression ‘std::is_integral<_Tp>::value [with _Tp = int*]’ evaluated to ‘false’
37 | concept Integral = std::is_integral<T>::value;
g++的错误提示,已经很容易看出来,inc函数传递指针是不满足concept的要求的。现状从inc函数的声明,也能直接看出对T的要求,T要是Integer类型。对于函数的设计者来说,你起一个比较直观的名字,就可以传达你的约束了。
上面concept只是一种写法,还有三种写法如下:
- 写法2
template <typename T>requires Integral<T>
T inc(T a)
{
return ++a ;
}
- 写法3
template <typename T>
T inc(T a) requires Integral<T>
{
return ++a ;
}
这种写法其实和写法2,差别不大,只是requires语句放的位置不同而已。
- 写法4
Integral auto inc(Integral auto a)
{
return ++a ;
}
这种也是我们的Bjarne Stroustrup老爷子最喜欢的语法。虽然看上去比较怪异,template关键字没了,多了auto关键字,也许是从c++的实现角度,使用auto关键字比较统一吧。
我觉得第一种写法,比较符合承上启下,与template语法差异不大,没有那么大的语法跳变,比较容易理解。
讨论
一种concept特性,四种写法,买一赠三,不带含糊的,就看你脑袋瓜子够不够用。之所以,为啥要有四种写法。接下来再好好学习,知其然知所以然吧。
网友评论