详解 C++ 的隐式类型转换与函数重载( 三 )


7.2 显式模板类型,显式模板实例化与隐式类型转换
对于模板中的任何显式类型部分,其都遵循隐式类型转换规则 。这主要分为两种情况:
1. 显式类型形参
参考以下代码:
template <int = 0>void test(int) {}int main{test(0.); // double -> int}上述代码中,虽然test是模板函数,但其第一参数是一个明确的int类型,故传入的double类型实参将通过隐式类型转换被转换为int类型 。
2. 显式模板实例化
参考以下代码:
template <typename T>void test(T) {}int main{test<int>(0.); // 强制调用void test(int)版本,double -> int}上述代码中,我们通过显式模板实例化,强行构造并调用了一个void test(int)版本的函数 。则此时,实参的double类型将通过隐式类型转换被转换为int类型 。
7.3 引用折叠
引用折叠是另一种较为复杂的类型转换 。参考以下代码:
template <typename T>void test(T &) {}template <typename T>void test(T &&) {}int main{const int &a = 0;const int &&b = 0;test(a); // 调用void test(int & &) -> 折叠为void test(int &)test(b); // 调用void test(int && &) -> 折叠为void test(int &)test(std::move(a)); // 调用void test(int & &&) -> 折叠为void test(int &)test(std::move(b)); // 调用void test(int && &&) -> 折叠为void test(int &&)}当模板参数声明为一个引用,且调用参数也为一个引用时,模板实例化出的参数类型将出现“引用的引用”,这包括以下四种情况:

  1. 使用T &调用T &:T & &
  2. 使用T &&调用T &:T && &
  3. 使用T &调用T &&:T & &&
  4. 使用T &&调用T &&:T && &&
当出现以上情况时,通过模板实例化出的函数将发生引用折叠 。情况1,2,3将折叠为T &,情况4将折叠为T && 。
需要注意的是,引用折叠只是对实参的适配手段,并不改变T的类型 。即:如果使用T &调用T &&,则T的类型就是T & 。
3.函数模板的重载确定
对于模板重载,首先需要明确以下几个要点:
  1. 模板也可以重载,各模板函数之间的函数签名应互不相同
  2. 模板函数与非模板函数可并存,共同作为同一重载体系
  3. 模板特化隶属于某个模板函数的强制实例化版本,与函数重载无关(重载确定后,如果确实调用了具有模板特化的模板函数,此时才会考虑模板特化)
如果重载函数中具有模板函数,则此时重载确定同样遵循普通函数的重载确定规则,以及以下的几点新规则:
  1. 精确匹配的非模板函数的优先级大于有能力通过实例化得到精确匹配的模板函数
  2. 普适性更低的模板函数的优先级大于普适性更高的模板函数
首先我们讨论上述第一点规则,参考以下代码:
template <typename T>void test(T) {}template <>void test(double) {}template <typename T>void test(T, T) {}template <>void test(int, int) {}void test(double) {}void test(int, double) {}int main{test(0.); // 调用非模板函数void test(double)test(0, 0.); // 调用非模板函数void test(int, double)test(0); // 调用void test(T)实例化得到的void test(int)test(0, 0); // 调用void test(T, T)的特化版本void test(int, int)}上述代码中,我们为test函数定义了4个重载版本,包括两个模板函数以及两个非模板函数,此外,我们还定义了两个模板特化函数,下面分别讨论对test函数的四种调用情况:
1. test(0.)
对于此调用,候选函数包括:
  • void test(T)模板函数(其有能力实例化出一个精确匹配的void test(double),但这不于重载确定阶段考虑)
  • void test(double)非模板函数
根据上文“精确匹配的非模板函数的优先级大于有能力通过实例化得到精确匹配的模板函数”这一规则,虽然void test(T)模板函数能够实例化出一个精确匹配的void test(double)函数,但由于存在一个精确匹配的非模板函数,故编译器将选择此非模板函数 。
2. test(0, 0.)
与test(0.)的情况类似,虽然模板函数void test(T, T)能够实例化出一个精确匹配的void test(int, double)版本,但由于存在一个精确匹配的非模板函数函数,编译器将选择此版本 。
3. test(0)
对于此调用,候选函数包括: