文章目录
五、模板
>> [ 1 ]. 模板参数推断
-
在c++17之前,类模板的参数必须显式指定,而c++17中类模板和函数模板一样,可以从初始化构造函数参数中推断模板参数类型
-
在c++17之后, 如果类模板所有参数能全部推断出来(包含有默认模板参数的情况) (形参不含引用的情况)则生成对象时不需要指定尖括号
<>
-
std::vector v(8,2)
//自动推断为vector<int>
类型 -
在c++17中 允许auto或decltype(auto)用于模板的非类型参数推断
template<auto N>
class A {
…
};
A<42> s1; // OK: type of N in A is int
A<‘a’> s2; // OK: type of N in A is char -
不能从函数形参的默认值处(包括构造函数) 推断
#include <iostream> using namespace std; template <typename T, typename U> void func(T val1 = 0, U val2 = 0) { //... } int main() { func(5,6); //ok func(); //编译报错 return 0; }
-
模板参数的推断规则本质上和 auto 一样, 模板类型参数
T
中是不保留原类型中的引用的,除非你显式指明:func<int&,int&&>
>> [ 2 ]. 默认模板参数
-
#include <iostream> using namespace std; template <class T=short,class Z=double,class U=char,auto N=5> Z func(Z val) { T t{}; Z z{}; U u{}; decltype(N) n {}; //用decltype()推导右值N的类型 cout<<N<<endl; return val; } int main() { //c++17之后允许传递给非类型参数 非全局作用域内此被static修饰的 局部变量的地址 static int temp{}; func(97); // T=short, Z=int, U=char, decltype(N)= int func<char>(97); // T=char, Z=int, U=char, decltype(N)= int func<double,long>(97); // T=double, Z=long, U=char, decltype(N)= int func<double,long,short,&temp>(97); // T=double, Z=long, U=short, decltype(N)= int* return 0; //为模板参数指定默认值时可以是在参数列表的开头, 中间 或 结尾 }
>> [ 3 ]. 模板的非类型参数
- c++17对模板的非类型参数的规定有所更改(更好用了)
- 模板的非类型参数可以是以下类型
>.指针变量必须用constexpr修饰才能传入 要么就用&取变量的地址直接传入 用auto修饰的非类型参数可以自动推断为该变量的指针类型
- 整型/字符型
- 对象类型(可以是模板对象)和基本类型的引用或指针
- 函数(可以是函数模板的实例化函数)的引用或指针
- 类的成员指针
- 用法示例:
#include <iostream> using namespace std; //类模板 template <class Z> class x { //.... }; //函数模板 template <class Z> void func(Z z){ //... } //普通函数 void func1(char ch){ // //... } //测试非类型参数用 函数模板 template <class Z, decltype(auto) N> Z test(Z val) { decltype(N) n = N; return val; } //传指针变量时必须用constexpr修饰为常量 表示后面出现pfunc时 可以自动转换为指向的函数的地址常量 constexpr auto pfunc =func<double>;//必须显示指明,不然不会有这个版本的函数,模板只是个"图纸" auto& pfunc1 =func1; //普通函数的引用 传引用类型时可不用constexpr修饰 constexpr int temp = 5; //基本类型 使其成为编译时常量即可传入模板的非类型参数 auto & temp1 = temp; //基本类型的引用 constexpr auto pTemp = &temp; //基本类型的指针 必须用constexpr声明为常量指针 才能传入 int main() { static x<short> obj; //或者在全局范围声明就不用加static static x<short>& obj1 = obj; decltype(auto) testType = pfunc1; //各个版本对非类型参数的要求略有不同 c++98要求必须将temp声明在全局, //即使用static修饰也无济于事 直到c++17 (至少gcc下如此) test<int,pTemp>(97); //pTemp为 const int*const 类型 N 为 const int* 类型 test<int,temp>(97); //temp为 const int 类型 N 为 int 类型 (把temp当做编译时常量所以没有const) test<int,&obj>(97); //&obj为 obj 对象地址 N 为 x<short>* 类型 test<int,obj1>(97); //obj1为 x<short>& 类型 N 为 x<short>& 类型 test<int,func1>(97); //func1为 普通函数 N 为 void(*)(char) 类型 test<int,func<int>>(97); //func为 函数模板实例化后的函数 N 为 void(*)(int) 类型 test<int,pfunc1>(97); //pfunc1为 普通函数的引用 N 为 void(&)(char) 类型 test<int,pfunc>(97); //pfunc为 函数模板实列化后的地址常量 指针 N 为 void(*)(double) 类型 return 0; //函数名会自动转换为地址常量 可以不用 & 取函数的地址 }
- 在模板内非类型参数是个右值不能修改
>> [ 4 ]. 无名模板参数
-
如果该模板体不需要用上模板参数则可以不写名字
template<typename,typename> //可以是一个/可以是多个 struct test { test(){cout<<"i do not need to use this/these type"<<endl;} };
-
通常是用在基模板上
template<typename,typename> struct test { test(){cout<<"i do not need to use this/these type"<<endl;} }; // 特化版本 template<typename _Tp,class A> struct test<_Tp,A&> //只要有一个参数不一样就能形成特化 { typedef _Tp type; test(){cout<<"Specialization"<<endl;} };
-
无名非类型模板参数
template<auto,auto> struct test { test(){cout<<"i do not need to use this/these non-type parameters"<<endl;} }; // 特化版本 template<char a,int b> struct test<a,b> { test(){cout<<"Specialization"<<endl;} };
>> [ 5 ].模板的可变参数
-
可变参数函数模板
template<typename... T> void vair_fun(T...args) { //函数体 }
- typename或者class后跟
…
就表明T
是一个可变模板参数,它可以接收多种数据类型,又称模板参数包。 args
参数的类型用T…
表示,表示 args 参数可以接收任意个参数,又称函数参数包。vair_fun();
可以这样调用,这表明 参数包可以是空包 相当于没有这个参数包- 使用可变参数模板的要点在于"解包", 使用包里的数据
- 递归方式解包
#include <iostream> using namespace std; //模板函数递归的出口 void vir_fun() { } template <typename T, typename... args> void vir_fun(T argc, args... argv)//把函数参数包放后面;vir_fun(argv...);这样调用时能自动分离 { cout << argc << endl; //开始递归,将第一个参数外的 argv 参数包重新传递给 vir_fun vir_fun(argv...); } int main() { vir_fun(1, "mytest.variadic_templates", 2.34); return 0; }
- 借助逗号表达式和初始化列表解包
#include <iostream> using namespace std; template <typename T> void dispaly(T t) { cout << t << endl; } template <typename... args> void vir_fun(args... argv) { //逗号表达式+初始化列表 int arr[] = { (dispaly(argv),0)... }; //{ (display(argv),0)... }会依次展开为 //{ (display(1),0), (display("mytest.variadic_templates"),0), (display(2.34),0) } //逗号表达式总是返回最后一个值,所以arr数组存储的都是0值 //arr 数组纯粹是为了将参数包展开,没有发挥其它作用 } int main() { vir_fun(1, "mytest.variadic_templates", 2.34); return 0; }
- 递归方式解包
- typename或者class后跟
-
可变参数类模板
- 借助逗号表达式和初始化列表解包
#include <iostream> using namespace std; template <typename T> void dispaly(T t) { cout << t << endl; } template<typename... Tail> class demo { public: demo(Tail... vtail) { int arr[] = { (dispaly(vtail),0)... }; } }; int main() { demo t(1, 2.34, "mytest.variadic_templates"); return 0; }
- 继承形式基类+构造函数递归解包
#include <iostream> //形式类模板demo 只起一个形式作用 中转作用 可以直接写成声明形式 template<typename... Values> class demo;//类型必须要用参数包申明符... 不然特化1 会 error! //特化1 模板特化/专门化, 因为模板参数包允许空包 继承式递归的出口 template<> class demo<> {}; //特化2 模板特化(含继承操作) 以继承的方式解包 template<typename Head, typename... Tail> class demo<Head, Tail...> : private demo<Tail...> //继承形式基类 { public: //demo<Tail...>(vtail...) 初始化形式基类 , 实际会跳转选择初始化 特化2 demo(Head v, Tail... vtail) : m_head(v), demo<Tail...>(vtail...) { dis_head(); } void dis_head() { std::cout << m_head << std::endl; } protected: Head m_head; }; int main() { //必须在<>内指明类型 别问 问就是 error demo<int, float, std::string> t(1, 2.34, "mytest.variadic_templates"); return 0; } /*输出: mytest.variadic_templates 2.34 1 */
注意:可以这样使用:
void commonF(int,double,char){}; template<typename... T> void templfun(T...args) { commonF(args...); } int main() { templfun(5,5.5,'a'); }
- 借助逗号表达式和初始化列表解包
-
Operator sizeof…()
C++11 also introduced a new form of the sizeof operator for variadic templates:
sizeof…
It expands to the number of elements a parameter pack contains.template<typename T,typename… Types>
void print(T firstArg,Types… args){
std::cout<<sizeof…(Types)<<endl; // print number of remaining types
std::cout<<sizeof…(args)<<endl; // print number of remaining args
}所以,你可能想:
#include <iostream> using namespace std; template <typename... args,typename T> void vir_fun(T argc, args... argv) { cout << argc << endl; if (sizeof...(args)>0) vir_fun(argv...); } int main() { vir_fun(1, "mytest.variadic_templates", 2.34,'a'); return 0; }
However, this approach doesn’t work, 因为 if 语句的两个分支在函数模板中都会被实例化
前文C++规定说过, 模板实例化哪个版本是在编译时决定的,而 if 的分支是有运行时特性的
用 if constexpr 代替即可 -
Fold Expressions
用法示例:Fold Expression Evaluation ( ... op pack ) ( ( ( pack1 op pack2 ) op pack3 ) ... op packN ) ( pack op ... ) ( pack1 op ( ... ( packN-1 op packN ) ) ) ( init op ... op pack ) ( ( ( init op pack1 ) op pack2 ) ... op packN ) ( pack op ... op init ) ( pack1 op ( ... ( packN op init ) ) ) #include <iostream> using namespace std; class Node { public: int m_a; Node* left; Node* right; Node(int i = 0): m_a(i), left(nullptr), right(nullptr) { } }; template <class T, typename... args> Node* traverse(T init, args... argv) { return (init ->* ... ->* argv); } auto Gleft = &Node::left; auto Gright = &Node::right; //-------------------------------------用法二-------------------------- template<class T> class AddSpace { T const& ref; public: AddSpace(T const& r): ref(r) {}; friend ostream& operator<<(ostream& os, AddSpace<T> s) { return os << ' ' << s.ref; } }; template<typename... Types> void print(Types const& ... args) { (cout << ... << AddSpace(args)) << endl; } int main() { Node* root = new Node{0}; root->left = new Node{1}; root->right = new Node{2}; traverse(root, Gleft, Gright); print(2, "hello world", 2.5); return 0; }
-
Variadic Expressions
template<typename... T> void printDoubled(T const&... args){ print(args + args...) //上一个例子中的print }
如果你调用:
printDoubled(7.5,string(“hello”),complex(4,2));
那么效果同下:print(7.5+7.5,
string(“hello”)+string(“hello”),
complex(4,2)+complex(4,2))还可这样用:
template<typename... T> void addOne(T const&... args){ print(args + 1...) //error print(args + 1 ...) //ok print((args + 1)...) //ok }
这样:
template<typename T1,typename... TN> constexpr bool isHomogeneous(T1,TN...){ //如果第一个参数和后面所有参数类型都相同 那么返回true return (std::is_same<T1,TN>::value && ...); // since c++17 }
-
Variadic Indices
template<class C,class... Idx> void printElems(C const& coll,Idx... idx){ print(coll[idx]...); }
when calling
vector coll={“good”,“times”,“say”,“bye”};
printElems(coll,2,0,3);
the effect is to call
print(coll[2],coll[0],coll[3]);
You can also declare nontype template parameters to be parameter packs.template<size_t... Idx,class C> void printElems(C const& coll){ print(coll[Idx]...); }
allows you to call:
print<2,0,3>(coll);
效果同上 -
Variadic Deduction Guides
For example, the C++ standard library defines the following deduction guide for std::arrays:
namespace std{ template<typename T,typename... U> array(T,U) -> array< enable_if_t<(is_same_v<T,U> && ...),T >, (1+sizeof...(U))>; }
-
Variadic Base Classes and Using
#include <string> #include <unordered_set> class Customer { private: std::string name; public: Customer(std::string const& n) : name(n) { } std::string getName() const { return name; } }; struct CustomerEq { bool operator() (Customer const& c1, Customer const& c2) const { return c1.getName() == c2.getName(); } }; struct CustomerHash { std::size_t operator() (Customer const& c) const { return std::hash<std::string>()(c.getName()); } }; // define class that combines operator() for variadic base classes: template<typename... Bases> struct Overloader : Bases... { using Bases::operator()...; // OK since C++17 }; int main() { // combine hasher and equality for customers in one type: using CustomerOP = Overloader<CustomerHash,CustomerEq>; std::unordered_set<Customer,CustomerHash,CustomerEq> coll1; std::unordered_set<Customer,CustomerOP,CustomerOP> coll2; //... }
>> [ 6 ].模板模板参数
- 模板也可以作为模板参数
//template<typename T> typename Thing中 Thing 是这个模板的名字其他是固定写法,参数列表需要匹配你传入的模板 template<template<typename T> typename Thing = std::array> //可以有默认值 class A{ };
- 使用示例:
#include <iostream> #include <array> using namespace std; template<class T,class U=int> class dx{ }; //alias template template<class T> using my_alias= T*; template<template <typename T> typename Thing=std::array> class B{ Thing<int> m_a; }; int main() { B<my_alias> a; B<dx> b; //因为 U 设有默认值 所以可以匹配 }
>> [ 7 ].Deduction Guides
-
#include <iostream> using namespace std; template <class T> class test { public: test() = default; test(T a) {}; }; test(const char *)->test<std::string>; //Deduction Guide int main() { static int temp{}; test a {"helloworld"};//const char* convert to string; }
-
Templatized Aggregates
Aggregate classes ( classes/structs with no user-provided, explicit, or inherited constructors, no private or protected nonstatic data members, no virtual functions, and no virtual, private, or protected base classes ) can also be templates.For example:template <class T>
class test
{
public:
T value;
string comment;
};Since C++17, you can even define deduction guides for aggregate class templates:
test(char const*,char const*)->test<std::string>;
test vc={“hello”,“initial value”};Without the deduction guide, the initialization would not be possible, because test has no consturct to perform the deduction aganist.