函数模板
1.定义
template<typename 类型形参1,typename 类型形参2, ...>
返回类型 函数模板名 (调用形参表) {
函数体
}
在函数模板的返回类型、调用形参表以及函数体中都可以使用该模板的类型形参。
例如:
template<typename A, typename b, typename _C>
A func (b arg) {
_C var;
...
}
2.使用
函数模板名<类型实参1, 类型实参2, ...> (调用实参表);
例如:
int i = func<int, double, string> (1.23);
char c = func<char, Student, long> (
Student ("张飞", 22));
#include <iostream> using namespace std; // 函数模板/模板函数 // 将类型参数化,T被称为类型参数 template<typename T> T max (T x, T y) { return x < y ? y : x; } int main (void) { int a = 123, b = 456; // cout << ::max<int> (a, b) << endl; cout << ::max (a, b) << endl; // 456 double c = 1.3, d = 4.6; // cout << ::max<double> (c, d) << endl; cout << ::max (c, d) << endl; // 4.6 string e = "hello", f = "world"; // cout << ::max<string> (e, f) << endl; cout << ::max (e, f) << endl; // world char g[] = "hello", h[] = "world"; // cout << ::max<string> (g, h) << endl; // cout << ::max (g, h) << endl; cout << ::max (string (g), string (h)) << endl; return 0; }
3.类型参数
1)类型形参:类型占位符,以字母或下划线开始,由字母、下划线或数组组成,不能包含特殊字符,也不能关键字冲突。每个类型形参前面必须冠以typename/class关键字,多个类型形参之间用逗号隔开。
2)类型实参:必须是具体类型,既可以是基本类型,也可以是类类型,甚至可以是模板实例化后的类型。一个类型实参可以不可以传递给一个类型形参,唯一的条件就是它必须满足函数模板的实现对该类型的要求。
#include <iostream> using namespace std; template<typename T> T add (T x, T y) { return x + y; } class Integer { public: Integer (int arg) : m_var (arg) {} Integer const operator+ ( Integer const& i) const { return m_var + i.m_var; } friend ostream& operator<< ( ostream& os, Integer const& i) { return os << i.m_var; } private: int m_var; }; int main (void) { cout << add<int> (123, 456) << endl; cout << add<double> (1.3, 4.6) << endl; cout << add<string> ("Hello, ", "World !") << endl; /* cout << add<char const*> ("Hello, ", "World !") << endl; */ cout << add<Integer> (123, 456) << endl; return 0; }
4.延迟编译
每个函数模板事实上至少要被编译两次。一次是在实例化之前,先检查函数模板本身,查看语法是否正确。另一次是在实例化期间,结合所使用的类型参数,再次检查模板代码,查看是否所有的调用都有效。但是请注意,只有在第二次编译时,才会真正产生二进制形式的机器指令。作为第一次编译的结果,仅仅是在编译器内部形成一个用于描述该函数模板的数据结构,即所谓的模板内部表示。这种对模板函数编译过程的特殊处理被称为延迟编译。
#include <iostream> using namespace std; class A { public: typedef unsigned int uint; class B {}; }; template<typename T> void foo (void) { typename T::uint u; typename T::B b; } int main (void) { A::uint u; A::B b; foo<A> (); return 0; }
5.隐式推断
如果函数模板调用参数(圆括号里的参数)的类型相关于该模板的模板参数(尖括号里的参数),那么在调用该函数模板时,即使不显式指定模板实参,编译器也有能力根据调用参数的类型隐式推断出正确的模板参数,以获得与普通函数调用一致的语法表达。但是要注意如果编译器隐式推断的类型和程序设计者所期望的类型不一致,可能导致结果错误,这时就不要再使用隐式推断,或者先做类型转换。
#include <iostream> #include <typeinfo> using namespace std; template<typename T> void foo (T x, T y) { cout << typeid (x).name () << ' ' << typeid (y).name () << endl; } template<typename T> void bar (T const& x, T const& y) { cout << typeid (x).name () << ' ' << typeid (y).name () << endl; } template<typename R, typename T> R hum (T x) { R y; cout << typeid (x).name () << ' ' << typeid (y).name () << endl; return y; } int main (void) { int a, b; foo (a, b); // i i double c, d; bar (c, d); // d d char e[256], f[256]; foo (e, f); // Pc Pc // e : char* // f : char* foo (&e, &f); // PA256_c PA256_c // e : char[256] // f : char[256] // &e : char (*)[256] // &f : char (*)[256] cout << sizeof (e) << endl; // 256 bar (e, f); // A256_c A256_c // e : char[256] // f : char[256] foo ("hello", "tarena"); // PKc PKc // bar ("hello", "tarena"); // A6_c A7_c // foo (a, c); // 隐式推断不能 同时隐式转换 foo<int> (a, c); // c : double->int foo<double> (a, c); // a :int->double foo (a, (int)c); // i i foo ((double)a, c); // d d c = hum<double> (a); // i d return 0; }
6.重载
1)和普通函数一样,函数模板也可以重载,在重载解析的过程中,编译器会优先选择类型约束性较强的版本。
2)模板函数和普通函数也可以构成重载,在重载解析的过程中,编译器会优先选择普通函数。
3)如果在调用具有重载关系的模板函数和普通函数时,提供了显式的类型实参表,那么编译器就不会再选择普通函数,而只是在模板函数中挑选类型约束性较强者。
4)显式指定的模板参数必须在所选择的重载版本中与调用参数的类型保持一致。
5)在函数模板的实例化函数中,编译器仍然优选选择普通函数,前提是该函数必须在函数模板被第一次编译时可见,否则将失去被选择的机会。
6)在重载函数模板时,应该尽可能把改变限制在参数的个数和类型的约束性上,对其引用属性最好始终如一,避免因为临时变量的产生而导致的非预期后果。
#include <cstring> #include <iostream> #include <typeinfo> using namespace std; // 两个任意类型值的最大值 template<typename T> T const& max (T const& x, T const& y) { cout << "<1" << typeid (x).name () << '>' << flush; return x < y ? y : x; } // 两个任意类型指针所指向目标的最大值 template<typename T> T* const& max (T* const& x, T* const& y){ cout << "<2" << typeid (x).name () << '>' << flush; return *x < *y ? y : x; } // 两个字符指针所指向字符串的最大值 char const* const& max ( char const* const& x, char const* const& y) { cout << "<3" << typeid (x).name () << '>' << flush; return strcmp (x, y) < 0 ? y : x; } /* char const* max (char const* x, char const* y) { cout << "<3" << typeid (x).name () << '>' << flush; return strcmp (x, y) < 0 ? y : x; } */ // 三个任意类型值的最大值 template<typename T> T const& max (T const& x, T const& y, T const& z) { cout << "<4" << typeid (x).name () << '>' << flush; return ::max (::max (x, y), z); } /* // 两个字符指针所指向字符串的最大值 char const* const& max ( char const* const& x, char const* const& y) { cout << "<3" << typeid (x).name () << '>' << flush; return strcmp (x, y) < 0 ? y : x; } */ int main (void) { int a = 123, b = 456; cout << ::max (a, b) << endl; cout << *::max (&a, &b) << endl; char const* x = "A"; char const* y = "AB"; cout << ::max (x, y) << endl; cout << ::max<> (x, y) << endl; cout << ::max<char const*> (x, y) << endl; char const* z = "ABC"; // cout << ::max (x, y, z) << endl; char const* const& r = ::max ( x, y, z); cout << r << endl; char const* m = "1"; char const* n = "12"; char const* o = "123"; ::max (m, n, o); cout << r << endl; return 0; }