C++模板扮演着重要的角色,所有的STL容器,算法,迭代器,适配器,所有的高级boost库都是用模板实现的。

C++模板用于两个不同的方面:类模板函数模板(包括类的成员函数模板)。比如:

虽然基本理念都是为了泛型,同一份代码(函数,容器等)支持多种类型,但它们在特性上有着本质的区别:

模板参数的使用差异

模板类的使用时,必须加上模板参数来实例化得到真正的类,不存在推断;而函数模板具有推断的特性,只需要提供无法推断出的类型来指明(当然无法推断的模板参数应该写在模板参数列表的前面),一般情况是,函数模板使用不需要提供模板参数,因为大部分情况,所有参数是可以推断出来的。

比如:

需要指定函数模板参数的例子:

template< typename ReturnType, typename >
ReturnType fun( const T& );

显然,我们对于ReturnType是无法推断的,使用fun这个函数就需要显式指定,另外注意一点,无法推断的模板参数需要写在前面,因为使用时指定的模板参数顺序与模板参数声明时的顺序一致。

比如:

#include <iostream>
#include <string>
using namespace std;
template< typename T, typename U >
U f( const T& t )
{
    cout << t << endl;
    return U();
}

// 无法推断的模板参数应该放在前面,这样方便指定
template< typename R, typename T >
R g( const T& t )
{
    cout << t << endl;
    return R();
}
int main()
{
    // 对于f为了指定返回U类型为int,必须提供整个模板参数
    f<string,int>( string("zhenshan") );
    
    // 指定U为int,而T自定推导为string
    g<int>( string("zhenshan") );
    return 0;
}

模板特化的差异

类模板的特化(或称专门化)和函数模板的特化有着本质的不同

类特化

类特化可以是偏特化,也可以是完全特化,注意,如果对类模板进行特化,整个类的内容需要重新书写。

举个例子:

#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <algorithm>
#include <assert.h>
#include <iterator>
using namespace std;

//原始模板定义,类带有两个模板参数
template< int N, typename Seq >
class Prop
{
public:
    Prop( const Seq& s ) :seq_(s) { init(); }
    typedef typename Seq::value_type value_type;
    value_type value;
private:
    const Seq& seq_;
    void init()
    {
        assert( seq_.size() > N );
        typename Seq::const_iterator cit = seq_.begin();
        advance( cit, N );
        value = *cit;
    }
};
 
// 偏特化,第二个模板参数用vector<bool>特化
template< int N >
class Prop<N, vector<bool> >
{
public:
    Prop( const vector<bool>& s ) :seq_(s) 
    {
        assert( seq_.size() > N ); 
        value = seq_[N]; 
    }
    
    typedef bool value_type;
    value_type value;
private:
    const vector<bool>& seq_;
};
int main()
{
    list<int> vect;
    for ( int i=0; i<10; ++i )
    {
        vect.push_back(i);
    }
    
    cout << Prop<3,list<int> >(vect).value << endl; //3
    vector<bool> bVect;
    bVect.push_back( true );
    bVect.push_back( false );
    bVect.push_back( true );
    cout << Prop<1, vector<bool> >(bVect).value << endl;// 0
    cout << Prop<2, vector<bool> >(bVect).value << endl;// 1
    return 0;
}

模板类的成员函数实例化惰性

没有使用的成员函数不会被实例化,这样有时候实现通用的包装非常方便

// 实现包装一个lambda函数的通用函数
#include <iostream>

template<typename F>
struct LambdaWrapper 
{
    LambdaWrapper(F const& f) : func_(f) {}
    void ReadCallback(int status, std::string data) { func_(status, data); }
    void WriteCallback(int status) { func_(status); }
    F func_;
};

template<typename F>
LambdaWrapper<F> Lambda( F const& f )
{
    return LambdaWrapper<F>(f);
}

int main()
{   
    auto f1 = Lambda( [](int n, std::string data)
    { 
        std::cout << "f1 status: " << n << 
            "; data: " << data << std::endl; 
    } );

    f1.ReadCallback(0, "hemmo world");

    auto f2 = Lambda( [](int n)
    { 
        std::cout << "f2 status: " << n << std::endl; 
    } );
    f2.WriteCallback(1);

    return 0;
}

输出:
f1 status: 0; data: hemmo world
f2 status: 1

关于函数模板的重载和全特化

一、函数模板的全特化,与类模板的全特化基本一致,不过,类模板的全特化,必须照样提供模板参数,比如,前面的代码,继续特化:

template<>
class Prop<0,string> // 需要显式提供模板参数
{// ...
};

而函数模板的全特化时,模板参数可加可不加。例如:

template< typename T, typename U >
T fun( const U& );

// 全特化:
template<>
void fun<void,string>( const string& ); //<void,string>可以省略

二、没有偏特化,只有重载,根本原因是编译器不允许函数模板定义时,部分指定模板参数。

// 例如,下面的函数模板定义是错的:
template< typename U >
int fun<int,U>( const U& ); // 出错,不允许出现<int,U>这样的函数模板参数

//应该直接:
template<typename U >
int fun( const U& ); // 在意思上,貌似是将T特化为int,但它不是上述函数模板的偏特化,而是重载。

三、存在函数模板重载时,进行全特化不能省略模板参数,否则到底是对哪个函数模板全特化,不确定,编译不能通过。调用时能自动选择最合适的函数。

四、主函数模板加入重载决议
主函数模板与普通函数一起加入重载决议,而模板函数的全特化不加入重载决议,并且:普通函数是一等公民,优先被选择;否则,寻找主函数模板中最佳的匹配,在选择了某个主函数模板的前提下,其特化版本才可能被选择。

例如:

// (1)
template<typename T>
void f(T);               // a
 
template<>
void f<int*>( int* );    // b
 
template<typename T>
void f(T*);              // c
 
int n=0;
f(&n);  // 调用C:因为根据重载决议,指针将最佳匹配主函数模板C

// (2)
template<typename T>
void f(T);               // a
 
template<typename T>
void f(T*);              // b
 
template<>
void f<int>( int* );     // c
 
int n=0;
f(&n);  // 调用C:因为根据重载决议优先匹配主函数模板b,然后恰好存在实参int*的特化,所以选择C

原则:

  1. 不要使用函数模板特化,因为晦涩的规则将降低代码的可读性。如果想针对实参特殊处理,直接使用普通函数。
  2. 如果确实想达到“偏特化”的效果,针对不同类型得到不同的实现,可以使用间接层包装:
template<typename T>
void f()
{
    f_impl<T>::f();
}
 
template<typename T>
struct f_impl
{
    static void f()
    {
       // ...
    }
};