在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&&>
#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;
//为模板参数指定默认值时可以是在参数列表的开头, 中间 或 结尾
}
>.指针变量必须用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;
//函数名会自动转换为地址常量 可以不用 & 取函数的地址
}
如果该模板体不需要用上模板参数则可以不写名字
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;}
};
可变参数函数模板
template<typename... T>
void vair_fun(T...args) {
//函数体
}
…
就表明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;
}
可变参数类模板
#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
*/
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;
}
//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 设有默认值 所以可以匹配
}
#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.