文章目录
本次整理的模板基础知识是: 模板函数的返回值类型, 非类型模板参数, 模板实参为可调用对象,来自《C++模板》 第二版。
模板函数的返回值类型
通过下面三种方法可以使编译器确定模板函数的返回值类型
1. 引入第三个模板参数用于指定返回值的类型
在实例化时需要显示指定返回值类型
template<typename T1,typename T2,typename RT>
RT max(T1 a,T2 b);
显示指定返回值类型max<double>(4,7.2)
,如下例子:
#include <iostream>
//这里如果写成template<typename T1,typename T2,typename RT>会编译不过
//因为下面实例化时,double参数被指定为第一个形参的类型
template<typename RT,typename T1,typename T2>
RT max(T1 a,T2 b) {
return b<a?a:b;
}
int main() {
auto v = max<double>(9.9,10);
std::cout<<v<<std::endl;
}
2. 让编译器自己推导出返回类型
template<typename T1,typename T2>
auto max(T1 a,T2 b) -> decltype (b<a?a:b) {
return b<a?a:b;
}
上面是C++ 11的写法,decltype
用于获取表达式的类型,通过decltype (b<a?a:b)
推导出返回值的类型,推导是编译期完成。还可以写成这样:
template<typename T1,typename T2>
auto max(T1 a,T2 b) -> decltype (true?a:b);
区别是decltype
内的表达式不同了,这里要注意b<a?a:b
和true?a:b
表达式是用于推导出类型,而不是通过表达式结果得出类型,具体的可以看看这个链接: why-do-these-two-code-snippets-have-the-same-effect。
下面是C++ 14及之后标准的写法,写法更简单些。
template<typename T1,typename T2>
auto max(T1 a,T2 b) {
return b<a?a:b;
}
3. 使用std::common_type
来获取返回值的类型,
它可以推导两种或多种不同类型的共同类型(即返回列表中类型参数都可以转换成的类型),将会返回一个数据结构包含一个类型成员,指示得到的类型,typename std::common_type<T1,T2>::type
,使用方法如下:
#include <type_traits>
template<typename T1,typename T2>
typename std::common_type<T1,T2>::type max(T1 a,T2 b) {
return b < a?a:b;
}
它的定义形式如下:
template <class... T>
struct comon_type;
如下例子:
#include <iostream>
#include <type_traits>
template<typename T1,typename T2>
typename std::common_type<T1,T2>::type max(T1 a,T2 b) {
return b < a ?a:b;
}
int main() {
//编译会报错,因为字符串与整型没有共同的类型
//auto v = max("123",10);
//通过整型9和浮点型10.1推导出返回类型为浮点型
auto v = max(9,10.1);
std::cout<<v<<std::endl;
}
可以通过cppinsights 查看模板实例化的结果,如下:
template<>
typename std::common_type<int, double>::type max<int, double>(int a, double b)
{
return b < static_cast(a) ? static_cast(a) : b;
}
可以看到推导出来的类型为double。
综合例子:
#include <iostream>
//1.引入第三个类型参数标识返回值
template<typename RT,typename T1,typename T2>
RT max1(T1 a,T2 b) {
return b<a?a:b;
}
//2.让编译器自己推导
//编译器自动推导
template<typename T1,typename T2>
auto max2(T1 a,T2 b) -> decltype(b<a?a:b) {
return b<a?a:b;
}
template<typename T1,typename T2>
auto max3(T1 a,T2 b) -> decltype(true?a:b) {
return b<a?a:b;
}
//3.用common_type
template<typename T1,typename T2>
typename std::common_type<T1,T2>::type max4(T1 a,T2 b) {
return b < a ?a:b;
}
int main() {
auto v1 = max1(9,10.1);
std::cout<<"v1:"<<v1<<std::endl;
auto v2 = max2(9,10.1);
std::cout<<"v2:"<<v2<<std::endl;
auto v3 = max3(9,10.1);
std::cout<<"v3:"<<v3<<std::endl;
auto v4 = max4(9,10.1);
std::cout<<"v4:"<<v4<<std::endl;
}
非类型模板参数
模板参数除了类型参数,可以使用非类型参数,如下,类模板Stack
的大小就是非类型参数
template<typename T,std::size_t Maxsize>
class Stack {
private:
std::array<T,Maxsize> elems;
...
};
显示指定类型和值 Stack<int,20>
。
如下定义了一个非类型参数的函数模板,结合std::transform
的使用示例:
#include <iostream>
#include <algorithm>
#include <vector>
template<int val,typename T>
T addValue(T x) {
return x + val;
}
int main() {
std::vector<int> s{1,2,3,4,5,6};
std::vector<int> d;
std::back_insert_iterator<std::vector<int>> bIt(d);
std::transform(s.begin(),s.end(),bIt,addValue<5,int>);
for(auto i:d) {
std::cout<<i<<std::endl;
}
}
非类型模板参数的限制
非类型模板参数,它们只能是:
- constant integral values(包括枚举)
- pointers to object/functions/members
- lvalue references to objects or functions
- std::nullptr_t
见如下例子:
#include <iostream>
class MyClass {
public:
MyClass(int v):_v(v) {
}
void printV() {
std::cout<<"v:"<<_v<<std::endl;
}
private:
int _v = 0;
};
enum enTest {
ONE = 0,
TWO = 1
};
//double类型不行
template <double v1>
double process(double v) {
return v*v1;
}
//string类型不行
template <std::string s>
void process1() {
std::cout<<s<<std::endl;
}
//string的左值引用可以
template <std::string& s>
void process11() {
std::cout<<s<<std::endl;
}
//整型可以
template <int v>
void process2() {
std::cout<<v<<std::endl;
}
//对象不行
template <MyClass c>
void process3() {
c.printV();
}
//对象的左值引用可以
template <MyClass& c>
void process33() {
c.printV();
}
//对象的指针可以
template <MyClass* c>
void process333() {
c->printV();
}
//枚举可以
template <enTest e>
void process4() {
std::cout<<e<<std::endl;
}
int main() {
process4<enTest::ONE>();
}
传入可调用对象
可以是函数,函数指针,函数对象,lambad。但是无法传入类的成员函数,需要通过std::invoker
(C++17)来支持。
template<typename Iter,typename Callable>
void foreach(Iter current,Iter end,Callable op) {
while(current != end) {
op(*current);
++current;
}
}