也可以为单独的函数写模板。语法与类模板类似。例如,可以写如下通用的函数来查找数组中的值并返回其索引:
template <typename T>
optional<size_t> Find(const T& value, const T* arr, size_t size)
{
for (size_t i{ 0 }; i < size; ++i) {
if (arr[i] == value) {
return i; // Found it; return the index.
}
}
return {}; // Failed to find it; return empty optional.
}
Find函数模板可以用于任何类型的数组。例如,可以用其查找int数组中的int的索引或SpreadsheetCell数组中的SpreadsheetCell。
可以用两种方式调用函数:用<>显式指定模板类型参数或省略类型让编译器从参数中推断类型参数。下面是例子:
int main()
{
int myInt{ 3 }, intArray[]{ 1, 2, 3, 4 };
const size_t sizeIntArray{ size(intArray) };
optional<size_t> res;
res = Find(myInt, intArray, sizeIntArray); // calls Find<int> by deduction.
res = Find<int>(myInt, intArray, sizeIntArray); // calls Find<int> explicitly.
if (res) { println("{}", *res); }
else { println("Not found"); }
double myDouble{ 5.6 }, doubleArray[]{ 1.2, 3.4, 5.7, 7.5 };
const size_t sizeDoubleArray{ size(doubleArray) };
res = Find(myDouble, doubleArray, sizeDoubleArray); // calls Find<double> by deduction.
res = Find<double>(myDouble, doubleArray, sizeDoubleArray); // calls Find<double> explicitly.
if (res) { println("{}", *res); }
else { println("Not found"); }
//res = Find(myInt, doubleArray, sizeDoubleArray); // DOES NOT COMPILE! Arguments are different types.
res = Find<double>(myInt, doubleArray, sizeDoubleArray); // calls Find<double> explicitly, even with myInt.
SpreadsheetCell cell1{ 10 };
SpreadsheetCell cellArray[]{ SpreadsheetCell{ 4 }, SpreadsheetCell{ 10 } };
const size_t sizeCellArray{ size(cellArray) };
res = Find(cell1, cellArray, sizeCellArray); // calls Find<SpreadsheetCell> by deduction.
res = Find<SpreadsheetCell>(cell1, cellArray, sizeCellArray); // calls Find<SpreadsheetCell> explicitly.
res = Find(myInt, intArray);
}
上面的Find()函数的实现要求作为其中的一个参数给出数组的大小。有时编译器是知道数组的确切大小的,例如,对于基于堆的数组。不必要传递数组的大小就能够调用Find()会比较好。可以通过添加如下的函数模板来达到目的。其实现只是将前面的Find()函数模板进行了间接调用。也演示了函数模板可以使用无类型参数,就像类模板一样。
template <typename T, size_t N>
optional<size_t> Find(const T& value, const T(&arr)[N])
{
return Find(value, arr, N);
}
Find()重载的语法看起来有一点儿奇怪,但是其用法是直接的,示例如下:
int myInt { 3 }, intArray[] {1, 2, 3, 4};
optional<size_t> res { Find(myInt, intArray) };
与类模板成员函数定义类似,函数模板定义(不仅仅是原型)必须对使用它们的所有源文件可用。这样的话,应该将定义放到模块定义接口文件中,如果不只一个源文件使用它们的话要进行导出。
最后,函数模板的模板参数可以有缺省省,与类模板类似。
注意:C++是标准库提供了std::find()函数模板,比这里示例的Find()函数模板要强大得多。
1、函数重载与函数模板
当要提供一个函数可以使用两种不同的数据类型时有两种选择:提供函数重载或提供函数模板。在这两种选项中怎么选择呢?
当写一个函数用于不同的数据类型时,函数体对于所有数据类型都是一样的,就提供函数模板。如果函数体对于不同的数据类型是不同,就提供函数重载。
2、函数模板重载
理论上,c++语言允许写函数模板特例,与写类模板特例一样。然而,很少会想这样做,因为这样的函数模板特例不参与重载解析,因此可能行为不可预知。
反之,可以用非模板函数或其他函数模板来重载函数模板。例如,你可以想要写一个Find()来重载const char*的C风格的字符串来用strcmp()进行比较,而不是用operator==,因为==只会比较指针,而不是真正的字符串。下面是这样的一个重载:
template <typename T>
optional<size_t> Find(const T& value, const T* arr, size_t size)
{
println("original");
for (size_t i{ 0 }; i < size; ++i) {
if (arr[i] == value) {
return i; // Found it; return the index.
}
}
return {}; // Failed to find it; return empty optional.
}
重载的函数可以使用如下:
int main()
{
// Using an array for word to make sure no literal pooling happens, see Chapter 2.
const char word[]{ "two" };
const char* words[]{ "one", "two", "three", "four" };
const size_t sizeWords{ size(words) };
optional<size_t> res{ Find(word, words, sizeWords) }; // Calls non-template find.
if (res) { println("{}", *res); }
else { println("Not found"); }
res = Find<const char*>(word, words, sizeWords); // Calls template with T=const char*.
if (res) { println("{}", *res); }
else { println("Not found"); }
}
对Find()的调用正确地找到了字符串”two”在索引1上。
如果显式地指定了模板类型参数如下,那么 函数模板就会用T=const char*,而不是const char*的重载:
res = Find<const char*>(word, words, sizeWords);
其对Find()的调用没有找到匹配的,因为它没有比较真正的字符串,而是比较的指针。
当编译器的重载解析过程有两个可能的候选项时,一个是函数模板,另一个是非模板函数,那么 编译器总是倾向于使用非模板函数。