美文网首页
01 函数模板

01 函数模板

作者: 奇点创客 | 来源:发表于2021-05-20 13:40 被阅读0次

函数模板示例

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

template <typename T>
T max(T a, T b) {
  return b < a ? a : b;
}

int main() {
  std::cout << ::max(1, 3);       // 3
  std::cout << ::max(1.0, 3.14);  // 3.14
  std::string s1 = "mathematics";
  std::string s2 = "math";
  std::cout << ::max(s1, s2);  // mathematics
}

Two-Phase Translation

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

模板实参推断(Template Argument Deduction)

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

std::cout << ::max(1, 42);  // int max<int>(const int&, const int&)
  • 实参的推断不允许自动类型转换,要求每个 T 都要正确匹配。传值调用参数时,cv 限定符会被忽略,引用转换为对应的值类型,数组或函数转换为指针
template <typename T>
T max(T a, T b) {
  return b < a ? a : b;
}

int a = 1;
const int b = 42;
int& c = a;
int arr[4];
std::string s;

::max(a, b);        // OK:T 推断为 int
::max(b, b);        // OK:T 推断为 int
::max(a, c);        // OK:T 推断为 int
::max(&a, arr);     // OK:T 推断为 int*
::max(1, 3.14);     // 错误:T 推断为 int 和 double
::max("hello", s);  // 错误:T 推断为 const char[6] 和 std::string
  • 两个参数类型不同时,有三个解决方案
    • 强制转换参数类型:max(static_cast<double>(1), 3.14)
    • 指定 T:max<double>(1, 3.14)
    • 用两个模板参数指定不同类型
  • 类型推断对默认实参无效,在圆括号的参数列表中指定T的默认实参是无效的
template <typename T>
void f(T = "");

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

f();  // OK

多种模板参数(Multiple Template Parameters)

  • 函数模板有两种参数,尖括号里的 T 叫模板参数(template parameter),参数列表里的 T 叫调用参数(call parameter),用来替换模板参数的各个对象叫模板实参,如 double
  • 模板参数数量不限,但不能指定默认的模板实参(对于函数模板而非类模板),如对于上述问题,可以指定两个类型,但返回类型为 T 不一定符合调用者的意图
template <typename T, typename U>
T max(T a, U b) {
  return b < a ? a : b;
}

auto m = ::max(1, 3.14);  // 返回类型由第一个实参决定
  • 模板实参不能推断返回类型,必须显式指定
template <typename T, typename U, typename RT>
RT max(T a, U b) {  // RT 不能被推断出
  return b < a ? a : b;
}

::max(1, 3.14);                       // 错误
::max<int, double, double>(1, 3.14);  // OK:显式指定 RT 为 double
  • 但前两个模板参数被推断,没必要显式指定,因此可以改变模板参数声明顺序,把 RT 放到最前,这样使用时只需要显式指定一个模板实参
template <typename RT, typename T, typename U>
RT max(T a, U b) {
  return b < a ? a : b;
}

::max<double>(1, 3.14);  // OK:返回类型为 double,返回 3.14
  • C++14 允许 auto 作为返回类型,它通过 return 语句推断返回类型
template <typename T, typename U>
auto max(T a, U b) {
  return b < a ? a : b;
}
  • 如果只支持 C++11,还需要指定尾置返回类型
template <typename T, typename U>
auto max(T a, U b) -> decltype(b < a ? a : b) {
  return b < a ? a : b;
}
  • 用 true 作为条件也一样,需要的只是推断类型而已
template <typename T, typename U>
auto max(T a, U b) -> decltype(true ? a : b) {
  return b < a ? a : b;
}
#include <type_traits>

template <typename T, typename U>
typename std::common_type<T, U>::type max(T a, U b) {
  return b < a ? a : b;
}
  • C++14 中能简写为
#include <type_traits>

template <typename T, typename U>
std::common_type_t<T, U> max(T a, U b) {
  return b < a ? a : b;
}
  • 对于较为复杂的类型,可以写到一个模板参数中
#include <type_traits>

template <typename T, typename U, typename RT = std::common_type_t<T, U>>
RT max(T a, U b) {
  return b < a ? a : b;
}
  • 有时 T 必须是一个引用,但返回类型是引用类型时可能出现问题,此时可以利用 std::decay去掉引用修饰符、cv 限定符等描述符,退化到最基本的类型
#include <type_traits>

template<typename T, typename U, typename RT = std::decay_t<decltype(true ? T() : U())>>
RT max(T a, U b) {
  return b < a ? a : b;
}
  • 调用时即可不指定模板参数
auto a = ::max(1, 3.14);

函数模板的重载

int max(int a, int b) { return b < a ? a : b; }

template <typename T>
T max(T a, T b) {
  return b < a ? a : b;
}

::max(1, 42);          // 调用函数
::max('a', 3.14);      // 调用函数
::max(1.0, 3.14);      // 通过推断调用 max<double>
::max('a', 'b');       // 通过推断调用 max<char>
::max<>(1, 42);        // 通过推断调用 max<int>
::max<double>(1, 42);  // 调用 max<double>
  • 返回类型的重载可能导致调用的歧义
template <typename T, typename U>
auto max(T a, U b) {
  return b < a ? a : b;
}

template <typename RT, typename T, typename U>
RT max(T a, U b) {
  return b < a ? a : b;
}

auto a = ::max(1, 3.14);               // 调用第一个模板
auto b = ::max<long double>(3.14, 1);  // 调用第二个模板
auto c = ::max<int>(1, 3.14);          // 错误:两个模板都匹配
// 模板一为double max<int, double>, 模板二为int max<int, double>
  • 指针或传统 C 字符串的重载
#include <cstring>
#include <string>

template <typename T>
T max(T a, T b) {  // 1
  return b < a ? a : b;
}

template <typename T>
T* max(T* a, T* b) {  // 2
  return *b < *a ? a : b;
}

const char* max(const char* a, const char* b) {  // 3
  return std::strcmp(b, a) < 0 ? a : b;
}

int main() {
  int a = 1;
  int b = 42;
  ::max(a, b);    // 调用 1,max<int>
  ::max(&a, &b);  // 调用 2,max<int>

  std::string s1 = "hello";
  std::string s2 = "world";
  ::max(s1, s2);  // 调用 1,max<std::string>

  const char* x = "hello";
  const char* y = "world";
  ::max(x, y);  // 调用 3
}
  • 如果用传引用实现模板,再用传值重载 C 字符串版本,不能用三个实参版本计算三个 C 字符串的最大值
#include <cstring>

template <typename T>
const T& max(const T& a, const T& b) {
  return b < a ? a : b;
}

const char* max(const char* a, const char* b) {
  return std::strcmp(a, b) < 0 ? b : a;
}

template <typename T>
const T& max(const T& a, const T& b, const T& c) {
  return max(max(a, b), c);  // 如果传值调用 max(a, b) 则出错
}

int main() {
  const char* a = "frederic";
  const char* b = "anica";
  const char* c = "lucas";
  ::max(a, b, c);  // run-time ERROR
}
  • 错误原因是 max(max(a, b), c) 中,max(a, b) 产生了一个临时对象的引用,这个引用在计算完就马上失效了
  • 重载版本必须在函数调用前声明才可见
#include <iostream>

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

template <typename T>
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
}

注意事项

  • 在模板中通常传值更好,这样语法简单,编译器优化更好,移动语义可以使拷贝更廉价(有时完全没有拷贝或移动),而且模板可能同时被简单和复杂类型使用,为简单类型选择复杂类型可能适得其反。尽管传字符串字面值常量或者原始数组容易出问题,但给它们传引用通常会造成更大的问题
  • 通常函数模板不需要声明为 inline,唯一例外的是特定类型的全特化。因为编译器可能忽略 inline,函数模板是否内联取决于编译器的优化策略
  • C++11 可以使用 constexpr 函数来生成编译期值
template <typename T, typename U>
constexpr auto max(T a, U b) {
  return b < a ? a : b;
}

int a[::max(sizeof(char), 1000u)];
std::array<int, ::max(sizeof(char), 1000u)> b;

相关文章

  • 模板基础知识

    阅读经典——《C++ Templates》01 函数模板 类模板 非类型模板参数 一些技巧 模板代码的组织结构 一...

  • 01 函数模板

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

  • c++具体化和实例化的区别

    首先:区分 非模板函数 模板函数 具体化非模板函数就是没有template模板函...

  • GEEKBAND C++ STL与模板编程

    C++ 函数模板 函数模板的声明和模板函数的生成 1.函数模板的声明 函数模板可以用来创建一个通用的函数,以支持多...

  • C++模板编程/泛型编程

    模板分类 模板分为函数模板与类模板两类。 函数模板 模板声明 模板定义 例如 函数模板实参类型不一致问题 参数推导...

  • Django自定义模板函数

    模板函数是使用于模板文件的处理函数,模板函数的使用方式是{% 模板函数 %} 1. 创建自定义函数文件夹 想要使用...

  • 14/12

    成员模板:模板类中成员变量是模板类对象(1),模板类中函数是模板函数(2)

  • 14/15

    约束模板友元 模板类的外边定义一个模板函数,在模板类中将模板函数具体化为模板类的友元函数 非约束模板友元 模板类中...

  • 第一章:函数模板

    函数模板是指被参数化的模板,可以代表一类函数。 1.1 初识函数模板 函数模板和普通函数看起来很相似,但是部分元素...

  • 2018-05-05

    CPP强弱类型 宽字符本地化 函数模板 函数重载: 根据参数来识别函数模板: 原生函数优先于模板函数 强行调用...

网友评论

      本文标题:01 函数模板

      本文链接:https://www.haomeiwen.com/subject/bjnvjltx.html