C++中有function overloading, function template, function template overloading,当出现一个函数调用的时候,compiler怎么决定改调用哪个函数?对于一个函数调用,compiler决定使用哪个定义好的函数的过程叫做重载解析(overlaod resolution)。过程如下:

  • 步骤1: 收集一系列的候选函数,候选集中的函数或者模板函数和被调用的函数有相同的函数名。
  • 步骤2: 从候选集中选出所有可行的函数。后选集中的函数有正确的参数数目,并且对于候选集中的函数,有一个顺序关系,顺序按照隐式的参数类型转换确定,其中包括实参类型和形参类型完全匹配的情况。
  • 步骤3:从候选集中选择最优可行函数,如果不存在,报错。

举个例子,如果出现了如下的函数调用

may('B'); // actual argument is type char

编译器首先会收集名为may的函数作为候选,同时要求这些候选中有相同的参数个数,注意收集的时候只考虑函数签名,不考虑return类型。

void may(int); // #1
float may(float, float = 3) ; // #2
void may(char); // #3
char * may(const char *); // #4
char may(const char &); // #5
template<class T> void may(const T &); // #6
template<class T> void may(T*); // # 7 

其中#4 和#7是不可行的,因为char不会隐式转换为指针类型。除此之外,其他的都是可行的函数。

下一步,compiler需要决定哪个可行函数是最优的。具体优先级的顺序如下:

  1. 完全匹配(exact match)。如果普通函数和模板函数都可以,优先选择普通函数。
  2. 实参类型提升之后可以匹配。conversions by promotion,例如char/short 提升为int,float提升为double。
  3. 实参通过标准转换(standard conversion)之后可以匹配。例如int 转为char,long转为double。
  4. 用户定义的类型转换,例如在class声明中定义的。

所以#1是优先于#2,因为char-to-int是promotion,char-to-float是standard conversion。

#3,#5,#6因为是exact match,因此优于#1和#2。

#3和#5因为是普通函数,优于#6模板函数。

但因为存在两个exact match(#3和#5),所以会报错。

问题来了,为什么#5也是exact match,形参明明是const char&。这是因为c++允许一些trivial conversions在exact match中。trivial conversions包括(下表中Type可以是任意类型,例如可以是int,也可以是char &

From 实参 To形参
Type Type &
Type & Type
Type [] * Type
Type (argument-list) Type(*)(argument-list)
Type const Type
Type volatile Type
Type * const Type *
Type * volatile Type *

举个例子:

struct blot { int a; char b[10]; };
blot ink = {25, "spots" };
...
recycle(ink);

///// exact match functions
void recycle(blot); // #1 blot-to-blot
void recycle(const blot); // #2 blot-to-(const blot)
void recycle(blot &) ; // #3 blot-to-(blot &)
void recycle(const blot &); // #4 blot-to-(const blot &)

以上四个都是exact match。

如果有多个exact match怎么办?

例如上述1-4都是exact match,compiler会报错。但是在以下情况的时候,多个exact之间也有优先级:

规则1: 虽然trivial conversion使得形参是const或者non-const都可以exact match,但是当形参包含引用或者指针的时候(#3和#4),对于non-const的实参,优先匹配non-const的形参。

所以,如果只有#1和#2,则会出现error,因为两个都可以exact match。同样,只有#1和#3的时候,也会出现error。但是当只有#3和#4的时候,只有#3会被compiler选择

规则2 : 优先匹配non-template的function

规则3:如果有多个template function都可以exact match,则优先匹most specialized: a) 对该类型特化的模板函数 b) compiler在做类型推倒的时候,用到更少的类型转换。

// 规则 3-a
struct blob { int a; char b[10]; };
template <class Type> void recycle (Type t); // template
template<> void recycle<blot> (blot& t); // spcilized for blot
...
blot ink = {25, "sports"};
...
recycle(ink); // use specialization

// 规则 3-b
template<class Type> void recycle(Type t); // #1
template<class Type> void recycle(Type * t); // #2
...
struct blot {int a; char b[10];};
blot ink = {25, "sports"};
...
recycle(&ink); // address of a blot

虽然recycle(&ink)exact match #1和#2,但是对于#1, Typeblot*,对于#2,Typeink。因此候选集中有:recycle<blot *>(blot*)recycle<blot>(blot*)。#2被认为更适合,因为#2已经显示指定function的形参是指向Type的指针,Type就是blot,但是对于#1, Typepointer-to-blot 。所以#2更为特化。

对于模板函数,compiler决定哪个函数最终被调用的过程,被称partial order rules,因为这些函数都是可以被调用的,所以需要定义一个偏序关系来确定最终调用的函数。

几个练习:

#include <cstring>
#include <string>

// maximum of two values of any type
template<typename T>
T max(T a, T b) {
  return b < a ? a : b;
}

// maximum of two pointers
template<typename T>
T* max(T* a, T* b) {
  return *b < *a ? a : b;
}

// maximum of two c string
char const* max(char const* a, char const* b){
  return std::strcmp(b, a) < 0 ? a : b;
}

int main() {
  int a = 7;
  int b = 42;
  auto m1 = ::max(a, b); // 匹配 第一个max - value
	
  std::string s1 = "hello";
  std::string s2 = "world";
  auto m2 = ::max(s1, s2); // 匹配第一个max - value
  
  int* p1 = &b;
  int* b2 = &a;
  auto m3 = ::max(p1, p2); // 匹配第二个max - pointer
  
  char const* x = "hello";
  char const* y = "world";
  auto m4 = ::max(x, y); // 匹配第三个max - c string
}
Reference

[1] C++ Primer Plus 6th Edition.