3_模板的细节改进(深入应用C++11代码优化与工程级应用)
1. 模板的右尖括号
在C++11之前,连续的两个右尖括号(>>)会被编译器解释为右移操作符,而不是模板参数表的结束。
C++11改进了编译器的解析规则,尽可能将多个右尖括号(>)解析成模板参数结束符,为我们编写模板相关代码提供了很大方便。
template <typename T>
struct Foo{
typedef T type;
};
template <typename T>
class A{
//...
};
int main()
{
Foo<A<int>>::type xx; // C++98
// error: '>>' should be '> >' within a nested template argument list
// Foo<A<int>>::type xx;
// ^
return 0;
}
$ g++ main.cpp -std=c++98
main.cpp: In function 'int main()':
main.cpp:18:14: error: '>>' should be '> >' within a nested template argument list
Foo<A<int>>::type xx;
^
根据错误提示,“>>”应该写成“> >”,在两个右尖括号中要添加一个空格。很显然这种限制是没有必要的。在C++11中,这种限制被取消了,编译器对模板的右尖括号进行单独处理,使编译器能够正确判断“>>”是模板的参数表结束符还是右移操作符。
2. 模板别名
我们通常会使用typedef将一个比较长的类型重新定义为一个短的类型,相当于给类型取了一个别名。
typedef unsigned int uint_t;
void func(unsigned int){}
void func(uint_t){} //error: redefinition of 'void func(uint_t)'
//void func(uint_t){
// ^
使用typedef重定义类型是是很方便的,但是有一些限制,比如,无法重定义一个模板。
typedef std::map<std::string, int> map_int_t;
typedef std::map<std::string, std::string> map_str_t;
template<typename Val>
typedef std::map<std::string, Val> map_t; //error: template declaration of 'typedef'
//typedef std::map<std::string, Val> map_t;
//^
在重定义模板时,需要使用外敷类。
template <typename Val>
struct str_map{
typedef std::map<std::string, Val> type;
};
str_map<int>::type map1;
str_map<std::string> map2;
使用using别名语法可以更简洁重命名。
template <typename Val>
using str_map_t = std::map<std::string, Val>;
str_map_t<int> map1;
str_map_t<std::string> map2;
实际上,using的别名语法覆盖了typedef的全部功能。由下面例子可以看出,在重定义普通类型上,两种方法的效果是等价的,唯一不同的是定义语法。
// 重定义unsigned int
typedef unsigned int uint_t;
using uint_t = unsigned int;
// 重定义std::map<std::string, int> map_int_t
typedef std::map<std::string, int> map_int_t;
using map_int_t = std::map<std::string, int>;
3.函数模板的默认模板参数
C++98/03中的类模板的默认模板参数:
template <typename T, typename U = int, U N = 0>
struct Foo{
//...
};
虽然支持类模板的默认模板参数,但是不支持函的默认模板参数:
template <typename T = int>
void func(void){
//...
}
$ g++ -std=c++98 demo01.cpp
demo01.cpp:7:15: error: default template arguments may not be used in function templates without -std=c++11 or -std=gnu++11
void func(void){
但是在C++11中确实被允许的。
#include <iostream>
using namespace std;
template <typename T = int, typename U = int>
class A{
};
template <typename T = int>
void func(void){
}
int main(void){
printf("hello world\n");
// A a;
// demo01.cpp:20:7: error: missing template arguments before 'a'
// A a;
// ^
A<> a;
func();
return 0;
}
从上面的例子可以看出,当所有模板参数都有默认参数是,函数模板的调用如同一个普通函数。但对于类模板而言,哪怕所有参数都有默参数,在使用时也必须在模板名后面跟随“<>”来实例化。
除了上述的不同之外,函数模板的默认模板参数在使用规则上和其他的默认参数也有些不同,如:默认的参数不一定都必须卸载模板参数列表的最后面。
所以,当默认模板参数和模板参数自动推导结合起来使用时,书写显得非常灵活。我们可以指定函数中的一部分模板参数采用默认参数,另一部分使用自动推导:
#include <iostream>
using namespace std;
template <typename R = int, typename U>
R func(U val){
}
int main(void){
printf("hello world\n");
func(123);
return 0;
}
需要注意的是,如果在使用函数模板时,显示指定模板的参数,参数填充的顺序是从左往右的:
func<long>(123); // 返回值的类型是long
函数模板的返回值类习性是long,而不是int,因为模板参数的填充顺序是从左往右的,所以指定的模板参数类型long会作为func的返回值类型为不是参数类型。
另外,当默认模板参数和模板参数自动推导同时使用是,若函数模板无法自动推导出参数类型,则编译器会使用默认模板参数;否则将使用自动推导出的参数类型:
#include <iostream>
using namespace std;
template <typename T>
struct identity{
typedef T type;
};
template <typename T = int>
void func(typename identity<T>::type val,T=0){
}
int main(void){
printf("hello world\n");
func(123);
func(123,123.0);
return 0;
}
在例子中,通过identyty外敷模板禁用了形参val的类型自动推导。但由于func指定了模板参数T的默认类型为int,所以在func(123)中,fucn的val参数将为int类型。而在func(123,123.0)中,由于func的第二个参数123.0为double类型,模板参数T将优先被推导为double,因此,此时val参数的类型为double类型。
因此,不要将默认参数当作模板参数自动推导的“建议”,因为模板参数自动推导总是根据实际参数推导得来的。当自动推导生效时,默认模板参数会被直接忽略。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构