模板别名

在C++98/03里,我们可以通过typedef 关键字定义一个类型的别名,比如 typedef unsigned int uint_t;在这里我们定义了unsigned int类型的别名和uint_t,在以后需要使用unsigned int的时候我们都可以用uint_t替换,但是uint_t仅仅是作为unsigned int的一个别名,如下的定义是不合法的:

1. typedef 
    unsigned 
    int 
    uint_t; 
   
2. void func(unsigned int); 
   
3. void func(uint_t);

上面的func函数是一个不合法的函数重载,虽然使用typedef定义一个类型的别名很方便,但是typedef在使用上存在一些限制,比如说typedef无法重定义一个模板的别名。

考虑下面例子,我们在实际编程中经常使用到STL中的MAP,我们MAP的string类型的数据作为MAP的key,我们想根据STRING类型的KEY映射为一个String,int,long等类型的数据.

1. typedef 
    std:: 
    map< 
    std:: 
    string, 
    int> 
    map_t; 
   
2. //...
3. typedef 
    std:: 
    map < 
    std:: 
    string, 
    std:: 
    string
4. //...

如果需要映射成10中类型的数据,我们就需要利用typedef定义10个具体类型的别名,但是考虑到MAP的key值始终是变的,我们是否像下面一样可以用typedef+模板来定义一个别名呢

1. template < 
    typename
2. typedef 
    std:: 
    map< 
    std:: 
    string, T> 
    map; 
   
3. map< 
    int> map_i; 
   
4. map< 
    std:: 
    string> map_str;

遗憾的是上述的定义不能通过编译,也就是C++ 98/03并不支持这样的操作,而通常是通过一个包裹类的方式来实现上述的需求:

通过包裹类的方法虽然可以实现上述的需求,但是一看代码就觉得,这个代码可读性差,不就是定义一个变量吗?还需要整一个包裹类来封装下,增加代码里不说,看着都烦,小幸运的是C++11终于让你可以不用通过上述这种臃肿的方式来实现这个需求了。C++11中,新增了一个特性就是可以通过使用using来为一个模板定义别名,比如说上述的需求,使用C++11就可以这样实现:

1. template < 
    typename
2. struct alias_map
3. 
     { 
   
4. typedef 
    std:: 
    map< 
    std:: 
    string, T> 
    map; 
   
5. 
     }; 
   
6.  
   
7. int>:: 
    map 
    map_t; 
   
8. int>:: 
    map
1. template < 
    typename
2. using alias_map = 
    std:: 
    map < 
    std:: 
    string, T > ; 
   
3. int> 
    map_t; 
   
4. std:: 
    string> map_str;

系不系看着舒服很多啊,顿时神清气爽,在C++11中,允许使用using关键字为一个模板来定义别名,实际上using包含了typedef的所有功能,来看下使用using关键字和typedef关键字定义普通类型别名的用法。

1. typedef 
    unsigned 
    int 
    uint_t; 
   
2. using 
    uint_t = 
    unsigned 
    int; 
   
3.  
   
4. typedef 
    std:: 
    map< 
    std:: 
    string, 
    int> 
    map_t; 
   
5. using 
    map_t = 
    std:: 
    map < 
    std:: 
    string, 
    int

可以看到在对普通类型的别名定义上,两种方法的使用基本等效,唯一不同的仅仅是定义的语法,using使用起来就像是赋值,但是在定义函数函数指针的时候,using看起来可读性要稍微好一点,比如:

1. typedef void(*func)(int, int); 
    
// 相当于给func定义了一个类型,此时不能省略1. 1. using func = 
    void(*)( 
    int, 
    int);// 相当于给func定义了一个类型,此时后面的void(*fun)(int, int)中的fun不能填写。

可能突然看起来使用using的方式来定义一个函数指针有点怪,但是习惯了之后会发现使用using这种赋值的方式更适用开发人员的思考方式。下面再显示一个通过typedef和using方式分别来定义一个函数模板的例子:

1. template< 
    typename
2. struct FuncSt
3. 
     { 
   
4. typedef void(*func)(T, T); 
   
5. 
     }; 
   
6.  
   
7. int>::func func_typedef; 
   
8.  
   
9.  
   
10. template< 
    typename
11. using func_using = 
    void(*)(T, T); 
   
12. int> func_using;

可以看到通过using定义模板别名的语法,仅仅是在普通类型别名语法基础上增加了template参数列表,通过using可以轻松的创建一个模板的别名,而不需要像C++98/03那样增加一个包裹类。但是需要额外注意的是使用using或者typedef仅仅是定义一个别名,不会创造新类型。




函数的默认模板参数

在C++98/03里,类模板是支持默认的模板参数的,比如:

1. template< 
    class T, class U = int, U n= 0>
2. struct 
    Foo
3. 
     { 
   
4. //
5. 
     }; 
   
但是在 
C++98/03中确实不能支持函数模板的默认模板参数: 
1. template < 
    typename T = 
    int> 
    //error in C++98/03 default template arguments
2. void func( 
    void) 
   
3. 
     { 
   
4. //...
5. 
     }

现在这个限制在

C++11中已经解除,上述的定义在C++11中可以直接使用了。在函数模板中当所有模板参数都有默认参数时,函数的调用就如同普通的函数调用,但是对于类末班而言,哪怕所有模板参数都有默认构造函数在使用时还是必须在模板名后跟随<>来实例化。

C++11中函数的默认模板参数在使用规则上和其他的默认参数也有一些区别,普通函数的默认参数必须写在参数列表的最后,而函数模板的参数就没有这个限制,因此当使用默认模板参数和模板参数自动推导时就显示十分灵活,可以指定函数中的一部分参数是默认参数,另一部分采用自动推导。比如:

1. template < 
    typename R = 
    int, 
    typename
2. 
     R func(U val) 
   
3. 
     { 
   
4. //...
5. 
     } 
   
6.  
   
7. int _tmain( 
    int
8. 
     { 
   
9. 123); 
   
10. return 
    0; 
   
11. 
     }

但是如果在使用函数模板时如果显示指定模板的参数,由于模板参数的填充顺序是

自左向右的,因此像下面这样的调用返回的类型是long类型:

func<long>(123); //func返回类型是填充类型long



这个细节虽然简单,但是在多个默认模板参数和多个模板参数自动推导穿插使用时会容易被忽略掉,造成使用上的一些意外,建议在使用的时候尽量还是价格默认模板参数写在模板参数的末尾;另外当默认模板参数和自动参数推导同时使用时,若函数模板无法推导出参数类型时,编译器将使用默认模板参数,否则将使用自动推导的参数类型。这个跟函数的默认参数使用规则是一致的,比较好理解。