C++ 模板

几种模板偏特化

除了减少模板参数个数的,还有限定范围的,比如T*指定指针类型优先使用这个偏特化版本

模板模板参数template template paramter

参数也是一个模板参数,内部可以用前一个T指定后一个模板类型,也就是实例化时不用指定第二个参数的参数,比如template<typename T, template<typename T> class Container>。但是不能使用默认值,也就是模板模板参数自己只能有一个参数,比如list<T, alloc=Alloc>有默认参数,不可以<string, list> m

可以用C++11的语法:template<typename T> using Lst=list<T, allocator<T>>,从而可以<string, Lst> m

注意template<typename T, typename S = List<T>>不是模板模板参数,因为第二个已经确定类型了,不再是模板

详见:c++11-17 模板核心知识(十二)—— 模板的模板参数 Template Template Parameters

可变参数模板varidic templates

需要注意省略号的位置:typename...Types...args...myFunc(std::forward<T>(args)...)

可以用sizeof...(args)获取args的参数个数

万能的print函数

1
2
3
4
5
6
7
8
void printX(){}	// 递归出口

template<typename T, typename... Types>
void printX(const T& firstArg, const Types&... args)
{
cout << firstArg << endl; // 输出第一个参数
printX(args...); // 处理剩余的参数
}

使用cout重写printf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void printf(const char* s)
{
while(*s){
if(*s == '%' && *(++s) != '%'){
throw std::runtime_error("invalid format string: missing arguments");
}
std::cout << *s++;
}
}
template<typename T, typename... Args>
void printf(const char* s, T value, Args... args)
{
while(*s){
if(*s == '%' && *(++s) != '%'){
std::cout << value;
printf(++s, agrs...); // call even when *s==0 to detect extra arguments ??
}
std::cout << *s++;
}
throw std::logic_error("extra arguments provided to printf");
}

万能max函数

如果参数类型相同,其实可以直接通过大括号调用std::max({1, 2, 3})

1
2
3
4
5
6
7
8
9
10
int maximum(int n)
{
return n;
}

template<typename... Args>
int maximum(int n, Args... args)
{
return std::max(n, maximum(args...)); // 这里仍然是比较int
}

递归继承实现tuple

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename... Valus> class tuple;
template<> class tuple<>{}; // 出口

template<typename Head, typename... Tail>
class tuple<Head, Tail...> : private tuple<Tail...> // 通过继承,把数据组合起来
{
typedef tuple<Tail...> inherited;
protected:
Head m_head; // 如果写到后面,会编译出错
public:
tuple(){}
tuple()(Head v, Tail... vtail)
: m_head(v), inherited(vtail...){}

typename Head head() { return m_head; }
inherited& tail() { return *this; } // 类型上转,抛弃头部元素,从而获得尾部基类数据
}

输出tuple中的元素,前后有中括号,使用逗号分隔,格式为:[a, b, c, ...]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<int MAX, typename... Args>
struct PRINT_TUPLE<MAX, MAX, Args>{
static void print(std::ostream& os, const tuple<Args...>& t){} // 递归出口
}

template<int IDX, int MAX, typename... Args>
struct PRINT_TUPLE{
static void print(ostream& os, const tuple<Args...>& t){
os << get<IDX>(t) << (IDX+1 == MAX ? "" : ","); // 最后一个元素不输出逗号
PRINT_TUPLE<IDX+1, MAX, Args...>::print(os, t);
}
}

template<typename... Args>
ostream& operator<<(ostream& os, const tuple<Args...>& t){
os << "[";
PRINT_TUPLE<0, sizeof...(Args), Args...>::print(os, t); // 开始递归
return os << "]";
}

万能的hash函数

使用方法:可以在使用各unordered容器时指定模板参数Hash,或者在std命名空间中为自定义类型特化hash结构体并实现size_t operator()(const MyClass& o) const noexpect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template<typename T>
inline void hash_combine(size_t& seed, const T& val){
seed ^= std::hash<T>()(val) + 0x9e3779b9 // 与黄金分割率相关0x9e3779b9 / 0x100000000约为0.618
+ (seed << 6) + (seed >> 2);
}

template<typeame T>
inline void hash_val(size_t& seed, const T& val){
hash_combine(seed, val);
}

template<typename T, typename... Types>
inline void hash_val(size_t& seed, const T& val, const Types&... args){
hash_combine(seed, val);
hash_val(seed, args...); // 递归
}

template<typename... Types>
inline size_t hash_val(const Types&... args){
size_t seed = 0;
hash_val(seed, args...); // seed传的是引用
return seed;
}

不定长参数委托

模板函数参数&&

对于参数是TT...的的模板函数,一般只需要写一个参数类型为T&&...的版本,函数体内需要使用std::forward。因为这里T&&会根据传递的实参进行推导和引用折叠,实际形参类型可能是&也可能是&&,所以需要完美转发。

但是也有一种特殊情况,也就是手动进行了特化:

1
2
3
4
5
6
7
template<typename T>
void f(T&& arg) {}

int a = 0;
f(a); // OK,f的参数被自动推导为int&(从int& &&折叠而来)
f<int&>(a); // OK,跟第一个调用相同
f<int>(a); // error C2664: 'void f<int>(T &&)': cannot convert argument 1 from 'int' to 'T &&'

第三个函数调用就是手动执行了特化,函数参数类型是int&&(从int&& &&折叠而来),但是传递的实参是左值。为了避免错误,这种时候最好还是显式地指明参数类型:

1
2
3
4
5
template<typename T>
void f(T arg) {} // 依靠调用者自行特化

int a = 0;
f<int&>(a); // 手动特化

对于非模板函数,或者参数是xxx<T>的模板函数,它们的参数本身不算模板,需要写参数类型分别为const xxx&xxx&&的版本。后者的函数体内部需要使用std::move进行转移。因为这里函数参数本身并不算模板,左值就是左值,右值就是右值。

成员函数指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename TRet, typename TClass, typename... TArgs>
static TRet Func(TClass* obj, TRet(TClass::* pMemFunc)(TArgs...), TArgs... args)
{
if constexpr (std::is_same_v<TRet, void>)
{
(obj->*pMemFunc)(args...);
}
else
{
return (obj->*pMemFunc)(args...);
}
}

Holder h;
Func(&h, &Holder::nonStatic, 2); // 调用成员函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define MM(TClass, FuncName)\
template<typename TRet = decltype(declval<TClass>().FuncName()),\
typename TClass, typename... TArgs>\
static TRet TClass##FuncName(TClass* obj, TRet(TClass::* pMemFunc)(TArgs...), TArgs... args)\
{\
if constexpr (std::is_same_v<TRet, void>)\
{\
(obj->*pMemFunc)(args...);\
}\
else\
{\
return (obj->*pMemFunc)(args...);\
}\
}\

// 为成员函数声明一个静态版本
MM(Holder, nonStatic)

Holder h;
HoldernonStatic(&h, &Holder::nonStatic, 0);