函数模版


经常地,你会编写相同演算流程的函数,虽然参数类型不同,然而对象的协定相同:

bool greaterThan(int a, int b) {
    return a > b;
}

bool greaterThan(string a, string b) {
    return a > b;
}

这时就会希望,参数的类型也可以…呃…参数化,也就是ab的类型可以在调用时指定,而不是如上写死了两个版本,这时可以使用函数模版:

#include <iostream> 
using namespace std; 

template <typename T>
bool greaterThan(T a, T b) {
    return a > b;
}

int main() { 
    // 显示 0
    cout << greaterThan(10, 20) << endl;

    // 显示 1
    cout << greaterThan(string("xyz"), string("abc")) << endl;

    return 0; 
}

在这个范例中,greaterThan是个函数模版(function template),或称为泛型函数(generic function),定义模版时使用template,之后跟着模版参数列,typename定义了一个模版参数T,若有多个模版参数,各自都要使用typename来定义,每个模版参数以逗号区隔。

代码greaterThan(10, 20)创建了一个模版的实例(instance),相当于greaterThan<int>(10, 20),而greaterThan(string("xyz"), string("abc"))创建了另一个模版实例,相当于greaterThan(string("xyz"), string("abc"))

创建一个模版实例的意思是,编译器推断出T的类型,产生并编译了一个对应版本,这就是之所以名为模版的原因,也就是说编译器以你定义的模版为基础,为greaterThan(10, 20)创建了bool greaterThan(int, int),为greaterThan(string("xyz"), string("abc"))创建了bool greaterThan(string, string)

如果有某个版本,不想要编译器创建,而想要自行实现呢?可以明确地定义特化版本:

#include <iostream> 
using namespace std; 

template <typename T>
bool greaterThan(T a, T b) {
    return a > b;
}

template <>
bool greaterThan(string s1, string s2) {
    return s1.size() > s2.size();
}

int main() { 
    cout << greaterThan(10, 20) << endl;
    cout << greaterThan(string("xyz"), string("abc")) << endl;

    return 0; 
}

在这个例子中,也许你想比的是字符串的长度而不是字典顺序,为此创建了特化版本,因此编译器就不会自行创建bool greaterThan(string, string)的版本,而是使用你定义的特化版本,执行结果就都显示 0 了。

现在来看看一个需求,传递数组给函数时会使用指针,一个例子是:

#include <iostream> 
using namespace std; 

void printAll(int *arr, int len) {
    for(int i = 0; i < len; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

int main() { 
    int arr1[] = {1, 2};
    int arr2[] = {3, 4, 5};

    printAll(arr1, 2);
    printAll(arr2, 3);

    return 0; 
}

如果想在printAll中使用 for range 可行吗?就上例来说没办法,根据〈指针与数组〉与〈指针的指针〉的说明,必须改为以下:

#include <iostream> 
using namespace std; 

void printAll(int (*arr)[2]) {
    for(auto elem : *arr) {
        cout << elem << " ";
    }
    cout << endl;
}

void printAll(int (*arr)[3]) {
    for(auto elem : *arr) {
        cout << elem << " ";
    }
    cout << endl;
}

int main() { 
    int arr1[] = {1, 2};
    int arr2[] = {3, 4, 5};

    printAll(&arr1);
    printAll(&arr2);

    return 0; 
}

问题是解决了,然而也在参数上写死了数组长度,仔细看看两个函数的实现内容是相同的,这时你会想,编译器可以为函数模版推断出对应类型的版本,那可否推断出数组长度的值呢?可以喔!

#include <iostream> 
using namespace std; 

template <typename T, int L>
void printAll(T (*arr)[L]) {
   for(auto elem : *arr) {
       cout << elem << " ";
   }
   cout << endl;
}

int main() { 
    int arr1[] = {1, 2};
    int arr2[] = {3, 4, 5};

    printAll(&arr1);
    printAll(&arr2);

    return 0; 
}

在上例中,L并不是以typename定义,而是int,这称为模版的非类型参数(nontype parameter),编译器会试着为非类型参数推断出一个值,值的推断来源必须是个常数表达式,也就是静态时期可决定的值。

上例可以使用参考,令调用时更直觉一些,例如:

#include <iostream> 
using namespace std; 

template <typename T, int L>
void printAll(T (&arr)[L]) {
   for(auto elem : arr) {
       cout << elem << " ";
   }
   cout << endl;
}

int main() { 
    int arr1[] = {1, 2};
    int arr2[] = {3, 4, 5};

    printAll(arr1);
    printAll(arr2);

    return 0; 
}

实际上,T (&arr)[L]的定义是多此一举,既然都用了模版了,底下照样也行得通:

#include <iostream> 
using namespace std; 

template <typename T>
void printAll(T &arr) {
   for(auto elem : arr) {
       cout << elem << " ";
   }
   cout << endl;
}

int main() { 
    int arr1[] = {1, 2};
    int arr2[] = {3, 4, 5};

    printAll(arr1);
    printAll(arr2);

    return 0; 
}




展开阅读全文