数值模版参数与模版模版参数
模版可以接收(编译期常量)数值作为模版参数
template <int a>
template <int a>
template <typename T,T val>
template <typename T,T val>
int fun(int x){
return x + val;
}
- (C++17)
template <auto a>
template <auto a>
void fun(){
}
- (C++20)接收字面值类对象与浮点数作为模版参数
目前clang 12不支持接收浮点数作为模版参数(主要是由于浮点数的储存方式和舍入误差)
模版同时也可以接收模版作为模版参数
template<template<typename. T> class C>
- (C++17)
template<template<typename T> typename C>
- C++17开始,模板的模板实参考虑缺省模板实参( clang 12 支持程度有限)
别名模版
我们可以使用using
来引入别名模版
template <typename T>
using MyType = T;
int main() {
MyType<int> x;
}
- 为模版本身引入别名
template <class T> struct Alloc{}; template <class T> using Vec = std::vector<T,Alloc<T>>; Vec<int> v;
- 为类模版的成员引入别名
template <typename T> struct B{ using TP = T*; }; template <typename T> using MyPointer = typename B<T>::TP ; int main() { MyPointer<int> x; }
- 别名模版不支持特化,但可以基于类模版的特化引入别名,以实现类似特化的功能
template <typename T> struct B{ using type = T*; }; template<> struct B<int>{ using type = int &; }; template <typename T> using MyPointer = typename B<T>::type ;
变长模版
我们可以通过使用参数包来引入变长模版
template <typename... a>
void fun(){
}
int main() {
fun<int,double,char>();
}
关于变长模版的具体内容参考这里
在引入变长模版之后,我们需要关注一个非常重要的概念-包展开。我们可以通过包展开技术操作变长模版参数,具体的内容参考这里
void fun(){\
}
template <typename U, typename ... T>
void fun(U u, T... args){
std::cout << u << std::endl;
fun(args...);
}
int main() {
fun(1,2,"hello",'c');
// 结果为 1
2
hello
c
}
在C++17中引入了折叠表达式,可以对上述的代码进行简化
template <typename... T>
void fun(T... args){
((std::cout << args << std::endl),...);
}
int main() {
fun(1,2,"hello",'c');
// 结果为 1
2
hello
c
}
这里只是最简单的例子,具体的用法可以参考这里
注意,折叠表达式用于表达式求值,无法处理输入(输出)是类型与模板的情形
完美转发
在模版中可以使用foeward
函数,它通常与万能引用结合使用,可以同时处理传入参数是左值或右值的情形
void g(int &){
std::cout << "l-referernce\n";
}
void g(int &&){
std::cout << "r-referernce\n";
}
template <typename T>
void fun(T&& input){
std::cout << "Hello word\n";
g(std::forward<T>(input)); // 如果不加forward,由于右值引用的变量本身是左值,会导致不期望的结果
}
int main() {
int x = 3;
fun(x);
fun(3);
}
关于完美转发的具体使用方法,请参考这里
Lambda表达式模版
在C++20中引入了Lambda
表达式模版,具体可以参考这里
消除歧义
我们可以使用typename
与template
消除歧义
- 使用
typename
表示一个依赖名称是类型而非静态数据成员
template <typename T>
void fun(){
T::internal * p; //两种理解 1,指针 2,乘法
}
template <typename T>
void fun(){
typename T::internal * p; // 消除了歧义,只认为是指针
}
- 使用
template
表示一个依赖名称是模版
struct Str{
template<typename T>
static void internal(){
}
};
template <typename T>
void fun(){
T::internal<int>(); // 两种理解 1,T内部的模版函数 2,<理解为小于号 不幸的是编译器会理解为第二个
}
struct Str{
template<typename T>
static void internal(){
}
};
template <typename T>
void fun(){
T::template internal <int>(); // 消除了歧义,只认为是T内部的函数模版
}
变量模版(C++14)
从C++14开始引入了变量模版
template <typename T>
T pi = (T)3.1415926;
int main(){
pi<int>; // 3
pi<float>; // 3.1415926
}
template <typename T, unsigned v>
unsigned MySize = (sizeof(T) == v);