Modern C++ Design(第二章):Techniques
本文来自Modern C++ Design的第二章:技术,会记录以下技术内容:
Partial template specialization,模板偏特化
Local Classes,局部类
型别与数值间的映射性(Int2type Type2Type class templates)
编译期察觉可转换性和继承性
型别信息及一个容易上手的std::type_info外覆类(wrapper)
Select class template,编译器根据bool状态选择型别;
Traits,traits技术集合
2.1 编译期错误Compile-Time Assertions及定制的错误日志
(注:代码复现价值并不大,因为现在的编译器能直接检出reinterpret_cast的错误转换,报错日志和原书不同;)
考虑一段实现安全转型的代码: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using namespace std;
template <typename To,typename From>
To safe_reinterpret_cast(From from){
assert(sizeof(From)<=sizeof(To));
return reinterpret_cast<To>(from);
}
int main(){
int i = 5;
safe_reinterpret_cast<char*>(i);
return 0;
}
这段代码通过assertion避免了类型的下行转换,然而assertion是在运行期发生的,有这么一个理由有必要将错误提前到编译期:reinterpret_cast是不可移植的,reinterpret_cast会重新解释类型的比特含义,在不同平台下内存布局有差异,有机会导致结果的差异,总有办法来做到这一点,例如考虑通过创建数组形式,创建一个非正数数组是失败的:
1
2
3
4
5
6
7
8
template <typename To,typename From>
To safe_reinterpret_cast(From from){
CHECK(sizeof(From)<=sizeof(To));
return reinterpret_cast<To>(from);
}'CastError<false> error' has incomplete type
1
2
3
4
5
6
7
8template<bool>struct CastError;
template<>struct CastError<true>{};
template <typename To,typename From>
To safe_reinterpret_cast(From from){
CastError<sizeof(From)<=sizeof(To)> error;
return reinterpret_cast<To>(from);
}1
2
3
4
5
6
7
8
9
10
11
template <typename To,typename From>
To safe_reinterpret_cast(From from){
CHECK(sizeof(From)<=sizeof(To),Param_Too_Narrow);
return reinterpret_cast<To>(from);
}
2.2 Partial template specialization
模板特化分成全特化和偏特化,也分成类模板特化和函数模板特化;本节原创。
全特化
当template参数列表为空,模板所有参数都确定,就是全特化,模板原型可以仅声明不定义,除非要用到原始模板:
1
2
3
4
5
6
7template<typename T1, typename T2>
void func(T1 t1,T2 t2);
template<> //全特化
void func<int,double>(int t1,double t2){
cout<<"template<>"<<t1<<t2<<endl;
}
类的全特化也类似,值得一提的是,特化版本是一个相对独立的类,即模板类不会限制死特化版本的成员变量和函数,换言之你可以在特化版本不定义、新定义、修改成员变量和函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29template<typename T1,typename T2>
class Person{
public:
template<typename T3 = decltype(T1()+T2())>
T3 add(){
return a+b;
}
private:
T1 a;
T2 b;
};
template<> //全特化
class Person<int,double>{
public:
void add(){
cout<<"new add:"<<a+b+c<<endl;
}
void declare(){
cout<<"class Person<int,double>"<<endl;
}
private:
int a;
double b;
int c = 10;
};
模板类对特化类的一些限制有:
特化类是相对独立,不会继承模板类的成员函数/变量,你需要自行声明和重新实现它们;
特化指的是类特化,不能单独特化某一个成员函数,但可以单独特化模板成员函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
template<typename T1,typename T2>
class Person{
public:
template<typename T3 = decltype(T1()+T2())>
T3 add(){
cout<<"template<typename T3 = decltype(T1()+T2())>"<<a+b;
return a+b;
}
void declare(){ //不能被单独特化
cout<<"eeeee"<<endl;
}
private:
T1 a;
T2 b;
};
template<>
template<>
int Person<int,double>::add(){
cout<<"Template member-func"<<endl;
return (int)(a+b);
}
// void Person<int,double>::declare(){ ////报错,不能单独特化成员函数!
// cout<<"cccccc"<<endl;
// }
//main函数:
Person<int,double>person1; //OK
person1.add<int>(); //显示调用特化函数特化版本应该显式调用,以便编译器能够获知函数由模板特化而来;
模板类和特化类必须在同一个命名空间中;
特化类中也许存在特化的函数,其中成员函数的特化仍然遵循不能是偏特化;
偏特化
只有类存在偏特化行为,即仅确定一部分参数类型,总结起来有这样的用法:
原始模板: 1
2
3
4
5
6
7
8
9
10
11
12
13template<typename T1,typename T2>
class Person{
public:
template<typename T3 = decltype(T1()+T2())>
T3 add(){
cout<<"template<typename T3 = decltype(T1()+T2())>"<<a+b;
return a+b;
}
private:
T1 a;
T2 b;
};
确定一部分模板类型是特化:
1
2
3
4
5
6template<typename T1>
class Person<T1,double>{
void declare(){
cout<<"class Person<T1,double>"<<endl;
}
};将两种类型特化成相同类型也是特化:
1
2
3
4
5
6template<typename T>
class Person<T,T>{
void declare(){
cout<<"class Person<T,T>"<<endl;
}
};这里不针对偏特化,全特化也有这种非类型的特化,例如将bool特化成true/false,将int特化成特定的数字等:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <typename T,bool isBool, int Int>
//模板版本:
struct Test{
Test(){
cout <<"template <typename T,bool isBool, int Int>"<<endl;
}
};
//特化版本:将Bool、Int特化
template <typename T>
struct Test<T,true,1>{
Test(){
int status;
char* demangled = abi::__cxa_demangle(typeid(value).name(), nullptr, nullptr, &status); //取gcc类型名
cout <<demangled<<" struct Test<T,true,1>"<<endl;
}
T value;
};
当然,类型偏特化是最常用的,函数模板不支持偏特化,也可以通过重载完成这样的目的:
1
2
3
4
5
6
7
8
9template<typename T1, typename T2>
void func(T1 t1,T2 t2){
cout<<"template<typename T1, typename T2>"<<t1<<t2<<endl;
}
// template<typename T1> //错误!函数不能偏特化
// void func<T1,int>(T1 t1,int t2){
// cout<<"func<T1,int>"<<t1<<t2<<endl;
// }1
2
3
4template<typename T2>
void func(int t1,T2 t2){
cout <<"template<typename T1>"<<t1<<t2<<endl;
}1
2
3
4template<>
void func<int,double>(int t1,double t2){
cout<<"template<>"<<t1<<t2<<endl;
}1
2
3int t1 = 5;
double t2 = 9.999;
func(t1,t2);1
func<int,double>(t1,t2);