函数模板示例

最简单的例子如下。使用作用域运算符(::)表示指定使用全局命名空间中的 max 模板,而非 std::max

  1. #include <iostream>
  2. #include <string>
  3. template <typename T>
  4. T max(T a, T b) {
  5. return b < a ? a : b;
  6. }
  7. int main() {
  8. std::cout << ::max(1, 3); // 3
  9. std::cout << ::max(1.0, 3.14); // 3.14
  10. std::string s1 = "mathematics";
  11. std::string s2 = "math";
  12. std::cout << ::max(s1, s2); // mathematics
  13. }

Two-Phase Translation

  • 模板被编译两次,分别发生在实例化前和实例化期间
  • 实例化前检查模板代码本身,包括
    • 检查语法是否正确,如是否遗漏分号
    • 发现使用不依赖于模板参数的 unknown name(类型名、函数名……)
    • 检查不依赖于模板参数的 static assertion
  • 实例化期间,再次检查模板代码保证所有代码有效(如某个类型不支持 operator<),特别的是,会再次检查依赖于模板参数的部分
  1. template <typename T>
  2. void f(T x) {
  3. undeclared(); // first-phase compile-time error if undeclared() unknown
  4. undeclared(x); // second-phase compile-time error if undeclared(T) unknown
  5. static_assert(sizeof(int) > 10, "int too small"); // always fails if sizeof(int) <= 10
  6. static_assert(sizeof(T) > 10, "T too small"); // fails if instantiated for T with size <= 10
  7. }

模板实参推断(Template Argument Deduction)

  • 如果传递 int 给 const T&,T 会被推断为 int ```cpp template T max(const T& a, const T& b) { return b < a ? a : b; }

std::cout << ::max(1, 42); // int max(const int&, const int&)

  1. - 实参的推断**不允许自动类型转换**,要求每个 T 都要正确匹配。传值调用参数时,cv 限定符会被忽略,引用转换为对应的值类型,数组或函数转换为指针
  2. ```cpp
  3. template <typename T>
  4. T max(T a, T b) {
  5. return b < a ? a : b;
  6. }
  7. int a = 1;
  8. const int b = 42;
  9. int& c = a;
  10. int arr[4];
  11. std::string s;
  12. ::max(a, b); // OK:T 推断为 int
  13. ::max(b, b); // OK:T 推断为 int
  14. ::max(a, c); // OK:T 推断为 int
  15. ::max(&a, arr); // OK:T 推断为 int*
  16. ::max(1, 3.14); // 错误:T 推断为 int 和 double
  17. ::max("hello", s); // 错误:T 推断为 const char[6] 和 std::string
  • 两个参数类型不同时,有三个解决方案

    • 强制转换参数类型:max(static_cast<double>(1), 3.14)
    • 指定 T:max<double>(1, 3.14)
    • 用两个模板参数指定不同类型
  • 类型推断对默认实参无效:在圆括号的参数列表中指定T的默认实参是无效的

  1. template <typename T>
  2. void f(T = "");
  3. f(1); // OK:T 推断为 int,调用 f<int>(1)
  4. f(); // 错误:不能推断 T
  • 默认实参应该在尖括号的模板参数列表中声明
  1. template <typename T = std::string>
  2. void f(T = "");
  3. f(); // OK

多模板参数(Multiple Template Parameters)

函数模板有两种参数,尖括号里的 T 叫模板参数(template parameter),参数列表里的 T 叫调用参数(call parameter),用来替换模板参数的各个对象叫模板实参,如 double

  • 模板参数数量不限,但不能指定默认的模板实参(对于函数模板而非类模板),如对于上述问题,可以指定两个类型,但返回类型为 T 不一定符合调用者的意图 ```cpp template T max(T a, U b) { return b < a ? a : b; }

auto m = ::max(1, 3.14); // 返回类型由第一个实参决定

  1. - 模板实参不能推断返回类型,必须显式指定
  2. ```cpp
  3. template <typename T, typename U, typename RT>
  4. RT max(T a, U b) { // RT 不能被推断出
  5. return b < a ? a : b;
  6. }
  7. ::max(1, 3.14); // 错误
  8. ::max<int, double, double>(1, 3.14); // OK:显式指定 RT 为 double
  • 但前两个模板参数被推断,没必要显式指定,因此可以改变模板参数声明顺序,把 RT 放到最前,这样使用时只需要显式指定一个模板实参
  1. template <typename RT, typename T, typename U>
  2. RT max(T a, U b) {
  3. return b < a ? a : b;
  4. }
  5. ::max<double>(1, 3.14); // OK:返回类型为 double,返回 3.14
  • C++14 允许 auto 作为返回类型,它通过 return 语句推断返回类型
  1. template <typename T, typename U>
  2. auto max(T a, U b) {
  3. return b < a ? a : b;
  4. }
  • 如果只支持 C++11,还需要指定尾置返回类型(有点像rust的函数返回值)
  1. template <typename T, typename U>
  2. auto max(T a, U b) -> decltype(b < a ? a : b) {
  3. return b < a ? a : b;
  4. }
  • 用 true 作为条件也一样,需要的只是推断类型而已
  1. template <typename T, typename U>
  2. auto max(T a, U b) -> decltype(true ? a : b) {
  3. return b < a ? a : b;
  4. }
  1. #include <type_traits>
  2. template <typename T, typename U>
  3. typename std::common_type<T, U>::type max(T a, U b) {
  4. return b < a ? a : b;
  5. }
  6. //C++14 中能简写为
  7. #include <type_traits>
  8. template <typename T, typename U>
  9. std::common_type_t<T, U> max(T a, U b) {
  10. return b < a ? a : b;
  11. }
  • 对于较为复杂的类型,可以写到一个模板参数中
  1. #include <type_traits>
  2. template <typename T, typename U, typename RT = std::common_type_t<T, U>>
  3. RT max(T a, U b) {
  4. return b < a ? a : b;
  5. }
  • 有时 T 必须是一个引用,但返回类型是引用类型时可能出现问题,此时可以利用 std::decay去掉引用修饰符、cv 限定符等描述符,退化到最基本的类型
  1. #include <type_traits>
  2. template<typename T, typename U, typename RT = std::decay_t<decltype(true ? T() : U())>>
  3. RT max(T a, U b) {
  4. return b < a ? a : b;
  5. }
  6. //调用时即可不指定模板参数
  7. auto a = ::max(1, 3.14);

函数模板的重载

  1. int max(int a, int b) { return b < a ? a : b; }
  2. template <typename T>
  3. T max(T a, T b) {
  4. return b < a ? a : b;
  5. }
  6. ::max(1, 42); // 调用函数
  7. ::max('a', 3.14); // 调用函数
  8. ::max(1.0, 3.14); // 通过推断调用 max<double>
  9. ::max('a', 'b'); // 通过推断调用 max<char>
  10. ::max<>(1, 42); // 通过推断调用 max<int>
  11. ::max<double>(1, 42); // 调用 max<double>
  • 返回类型的重载可能导致调用的歧义
  1. template <typename T, typename U>
  2. auto max(T a, U b) {
  3. return b < a ? a : b;
  4. }
  5. template <typename RT, typename T, typename U>
  6. RT max(T a, U b) {
  7. return b < a ? a : b;
  8. }
  9. auto a = ::max(1, 3.14); // 调用第一个模板
  10. auto b = ::max<long double>(3.14, 1); // 调用第二个模板
  11. auto c = ::max<int>(1, 3.14);
  12. // 错误:两个模板都匹配
  13. // 模板一为double max<int, double>, 模板二为int max<int, int, double>
  • 指针或传统 C 字符串的重载
  1. #include <cstring>
  2. #include <string>
  3. template <typename T>
  4. T max(T a, T b) { // 1 基本类型
  5. return b < a ? a : b;
  6. }
  7. template <typename T>
  8. T* max(T* a, T* b) { // 2 类型指针
  9. return *b < *a ? a : b;
  10. }
  11. const char* max(const char* a, const char* b) { // 3 char*
  12. return std::strcmp(b, a) < 0 ? a : b;
  13. }
  14. int main() {
  15. int a = 1;
  16. int b = 42;
  17. ::max(a, b); // 调用 1,max<int>
  18. ::max(&a, &b); // 调用 2,max<int>
  19. std::string s1 = "hello";
  20. std::string s2 = "world";
  21. ::max(s1, s2); // 调用 1,max<std::string>
  22. const char* x = "hello";
  23. const char* y = "world";
  24. ::max(x, y); // 调用 3
  25. }
  • 如果用传引用实现模板,再用传值重载 C 字符串版本,不能用三个实参版本计算三个 C 字符串的最大值
  1. #include <cstring>
  2. template <typename T>
  3. const T& max(const T& a, const T& b) {
  4. return b < a ? a : b;
  5. }
  6. const char* max(const char* a, const char* b) {
  7. return std::strcmp(a, b) < 0 ? b : a;
  8. }
  9. template <typename T>
  10. const T& max(const T& a, const T& b, const T& c) {
  11. return max(max(a, b), c); // 如果传值调用 max(a, b) 则出错
  12. }
  13. int main() {
  14. const char* a = "frederic";
  15. const char* b = "anica";
  16. const char* c = "lucas";
  17. ::max(a, b, c); // run-time ERROR
  18. }
  • 错误原因是 max(max(a, b), c) 中,**max(a, b)** 产生了一个临时对象的引用,这个引用在计算完就马上失效了

  • 重载版本必须在函数调用前声明才可见 ```cpp

    include

template T max(T a, T b) { std::cout << 1; return b < a ? a : b; }

template T max(T a, T b, T c) { return max(max(a, b), c); // 即使对int也使用上述模板,因为没看见下面版本 }

int max(int a, int b) { //该函数需要放在模板函数前面才能被调用到 std::cout << 2; return b < a ? a : b; }

int main() { ::max(3, 1, 2); // 11 }

  1. <a name="1bbbb204"></a>
  2. ## 注意事项
  3. - 在模板中**通常传值更好,这样语法简单,编译器优化更好,移动语义可以使拷贝更廉价**(有时完全没有拷贝或移动),而且模板可能同时被简单和复杂类型使用,为简单类型选择复杂类型可能适得其反。尽管传字符串字面值常量或者原始数组容易出问题,但给它们传引用通常会造成更大的问题
  4. - 通常函数模板不需要声明为inline,唯一例外的是特定类型的全特化。因为编译器可能忽略 inline,函数模板是否内联取决于编译器的优化策略
  5. - C++11 可以使用 constexpr 函数来生成编译期值
  6. ```cpp
  7. template <typename T, typename U>
  8. constexpr auto max(T a, U b) {
  9. return b < a ? a : b;
  10. }
  11. int a[::max(sizeof(char), 1000u)];
  12. std::array<int, ::max(sizeof(char), 1000u)> b;