C++ template
For basic C++ template usage there are plenty of tutorials, so here will be shown features not used so often.
Template default parameters
Template parameter can have default type in the similar manner as function argument has default value. That is valid for template functions:
template<typename T, typename S = string> S f(T t, S s) { cout << "int f(T t, S s): t=" << t << ", s=" << s << endl; return s; } int main() { string s = f(1, "Hello, World!"); int i = f(0, 1); return 0; }
template<typename T, typename S = string> class C { public: C(T t, S s) : _t(t), _s(s) { cout << "C(T t, S s): t=" << t << ", s=" << s << endl; } private: T _t; S _s; }; int main() { C<int, int> c1(1, 2); C<int> c2(0, "Hello, World!"); return 0; }
c2
is created by specifying only one type parameter.
Function overloading on template arguments
Overloading on template type parameters is valid:
template<typename T> T f() { cout << "T f(): "; return T(); } template<typename T, typename S> S f() { cout << "S f(): "; return S(); }
Function overloading on return type
Return type is not part of signature of non-template function. So the code
int g() { return 1; } float g() { return 1.0; }
template<typename T> int f(T t) { cout << "int f(T t): t=" << t << endl; return 1; } template<typename T> bool f(T t) { cout << "bool f(T t): t=" << t << endl; return true; } int main() { int i = static_cast<int(*)(const char*)>(f<const char*>)("Hello"); bool b = static_cast<bool(*)(const char*)>(f<const char*>)("World"); return 0; }
main()
call to the function has to be casted, otherwise the compiler could not resolve it.
Function specialization
Specialization of template function can be tricky. Template like
template<typename T> T mid(T a, T b, T c);
template<> double mid(double a, double b, double c);
T*
:
template<typename T> T* mid(T* a, T* b, T* c);
T* mid(T* a, T* b, T* c)
overloads T mid(T a, T b, T c)
.
That characteristic will be used in the example below.
If one defines
template<> const char* mid(const char* a, const char* b, const char* c);
T mid(T a, T b, T c)
or T* mid(T* a, T* b, T* c)
? It appears that it depends of the
specializations order. For the case
template<typename T> T mid(T a, T b, T c); // (1) template<> double mid(double a, double b, double c); template<typename T> T* mid(T* a, T* b, T* c); // (2) int mid(int a, int b, int c); template<> const char* mid(const char* a, const char* b, const char* c); // (3)
mid("hello", "all", "world")
calls (3).
For the case
template<typename T> T mid(T a, T b, T c); // (1) template<> double mid(double a, double b, double c); template<> const char* mid(const char* a, const char* b, const char* c); // (3) template<typename T> T* mid(T* a, T* b, T* c); // (2) int mid(int a, int b, int c);
mid("hello", "all", "world")
calls (2).
Detailed explanation can be found on Why Not Specialize Function Templates. Shortly, the order of specializations goes like this:
- Non-template function that matches parameter types is selected over an otherwise-just-as-good function template.
- If there is no such non-template function, then the most appropriate template function is selected. The most appropriate means the template that "looks most like" the given function call.
- If such template is specialized for a concrete type, that specialization is used.
- If there are several base templates or there is no one that can be made to match, compiler reports the error.
Full sample can be found in
#include <cstdlib> #include <iostream> #include <utility> #include <cstring> using namespace std; template<typename T> T mid(T a, T b, T c) { cout << "template<typename T> T mid(T a, T b, T c): "; if (b < a) swap(a, b); if (b > c) swap(b, c); if (b < a) swap(a, b); return b; } // specializes T mid(T a, T b, T c) template<> double mid(double a, double b, double c) { cout << "template<> double mid(double a, double b, double c): "; if (b < a) swap(a, b); if (b > c) swap(b, c); if (b < a) swap(a, b); return b; } // overloads T mid(T a, T b, T c) template<typename T> T* mid(T* a, T* b, T* c) { cout << "template<typename T> T mid(T* a, T* b, T* c): "; if (*b < *a) swap(a, b); if (*b > *c) swap(b, c); if (*b < *a) swap(a, b); return b; } // plain old function int mid(int a, int b, int c) { cout << "int mid(int a, int b, int c): "; if (b < a) swap(a, b); if (b > c) swap(b, c); if (b < a) swap(a, b); return b; } // specializes T mid(T* a, T* b, T* c) template<> const char* mid(const char* a, const char* b, const char* c) { cout << "template<> char* mid(char* a, char* b, char* c): "; if (strcmp(b, a) < 0) swap(a, b); if (strcmp(b, c) > 0) swap(b, c); if (strcmp(b, a) < 0) swap(a, b); return b; } int main() { cout << mid(1, 2 ,3) << endl; cout << mid<int>(2, 1 ,3) << endl; cout << mid(2.4, 3.1, 1.7) << endl; cout << mid("hello", "all", "world") << endl; return EXIT_SUCCESS; }
int mid(int a, int b, int c): 2 template<typename T> T mid(T a, T b, T c): 2 template<> double mid(double a, double b, double c): 2.4 template<> char* mid(char* a, char* b, char* c): hello
int mid(int a, int b, int c): 2 template<typename T> T mid(T a, T b, T c): 2 template<> double mid(double a, double b, double c): 2.4 template<typename T> T mid(T* a, T* b, T* c): hello
All samples were tested with gcc 4.8.1 and clang 3.3 under Linux.
Templates and statics
Static variable is instantiated per template instance. Function template
template<typename T> void f(T t) { static T s = t; s *= 2; if (typeid(T) == typeid(int)) cout << "f<int>(T t): s=" << s << endl; else if (typeid(T) == typeid(double)) cout << "f<double>(T t): s=" << s << endl; else cout << "f<T>(T t): s=" << s << endl; } int main() { f(1); f(3); f(1.4); f(6.2); f(7); f(9.1); return 0; }
s
variables for each of the instances:
f<int>(T t): s=2 f<int>(T t): s=4 f<double>(T t): s=2.8 f<double>(T t): s=5.6 f<int>(T t): s=8 f<double>(T t): s=11.2
template<typename T> class C { public: static void dbl() { t *= 2; if (typeid(T) == typeid(int)) cout << "C<int>: t=" << t << endl; else if (typeid(T) == typeid(double)) cout << "C<double>: t=" << t << endl; else cout << "C<T>: t=" << t << endl; } static T t; }; template<typename T> T C<T>::t; int main() { C<int> ci1, ci2; ci1.t = 1; ci1.dbl(); ci2.dbl(); C<double> cd1, cd2; cd2.t = 1.6; cd2.dbl(); cd2.dbl(); ci1.dbl(); cd1.dbl(); return 0; }
C<int>: t=2 C<int>: t=4 C<double>: t=3.2 C<double>: t=6.4 C<int>: t=8 C<double>: t=12.8