CTTCG 01 Function Templates

2023 年 5 月 7 日

两个 type traits

std::decay_t 可以去引用、去限定符、函数/数组变指针;std::common_type_t 用三元运算符获得更“宽泛”类型。

函数模板默认参数

模板默认参数可以放在最前面,不像普通函数只能把带有默认值的参数放在最后。而且函数模板可以明确指定开头几个参数,让后面的参数由推导规则生成。

函数重载参数匹配优先级

优先级排序

  1. perfect match
  2. decay,或者修改指针的内外层 const 属性(volatile 应该也算吧?) 比如 char *char const *
  3. promotion,比 int 小的转到 int 或更大的整数,或者 float 到 double。 举例:bool 到 char 不是 promotion,而是 standard conversion
  4. standard conversion,包括 int to float子类(值/引用/指针)向基类(值/引用/指针)转换
  5. 用户定义的转换,包括标准库的类
  6. 与可变长参数列表匹配,也就是 (...) 例外:f(...) f(void) 参数类型是同级别的,因为没有提供参数,编译器不知道哪一个匹配更合适。

以上参数匹配不涉及到值类型、左引用、右引用的区别。只要引用属性能够匹配,就不区分优先级。比如同时匹配到 f(int)f(int &) 编译器就会抱怨函数调用 ambiguous。

各个参数匹配优先级有差异时:如果一个函数重载的参数组的优先级均不低于另一个函数重载,则前一个函数重载更优先。

各个参数匹配优先级一样时:

  1. 非模板函数比模板函数更优先。
  2. 特化程度高的模板函数比特化程度低的模板函数更优先。
  3. 一般模板函数比模板参数中带有 parameter pack(实际上匹配为空)的模板函数更优先。

在传递参数时最多允许一次用户定义转换。如果函数重载 A 的参数类型转换序列是 B 的子序列,则函数重载 A 的优先级更高。

standard conversion 中的指针转换

  1. 转换成 bool
  2. 转换成 void *
  3. 子类指针向基类指针的转换
  4. 指向成员指针(pointers to members)从基类向子类的转换

这几类转换中优先级从 1 到 3 越来越高。第 3 点中,向基类指针转换时跨过的层级越少转换优先级越高。第 4 点也是,转换跨越的层次越少优先级越高。

由于成员是从基类继承下来的,所以从基类向子类转换是合理的。

初始化列表作为实参

可以尝试把初始化列表推导成:

  1. std::initializer_list
  2. 可以接受 std::initializer_list 作为参数调用构造函数的类
  3. 把初始化列表当成 uniform initialization 调用构造函数的类
  4. aggregate 的初始化

如果推导为 std::initializer_list<T>,那么本次匹配的优先级由初始化列表中各参数类型转换的最差优先级决定。比如 {'a', 'b', 0} 在推导成 std::initializer_list<char> 时,需要 0 从 int 转换成 char(标准转换);推导成 std::initializer_list<int> 时,需要前面的字符从 char 转换成 int(integer promotion)。因而匹配 f(std::initializer_list<int>) 比 匹配 f(std::initializer_list<char>) 优先级更高。

如果推导成 2 或 3(同一类),优先级如下:

  1. 如果初始化列表为空,且类具有可调用的默认构造函数,则匹配默认构造函数
  2. 考虑第 2 类
  3. 考虑第 3 类的所有其他构造函数

Surrogate Function

可以想象函数本身也是参数中的一个。上面的 funcObj 是一个 const 引用,不过 operator FuncType* 也恰好可以在 const 属性的 this 指针上调用,因而转换是可行的。由于需要额外的转换,所以第二个函数在参数 0 位置不如第一个函数匹配程度高(按照 IndirectFunctor 中的两个函数的顺序)。但是在参数 2 位置,FuncType 接受 int,比第一个函数匹配程度高,这就导致了两个函数无法决出优先级。

函数重载时名字可见性

在模板中使用了其他函数时,可以参与重载决议的函数必须在此之前。即便后面有匹配度更高的函数也无法使用。P19

std::ref

书中的一个观点是:函数模板的参数可以使用值类型,如果确实需要传递引用,由用户调用 std::ref 或者 std::cref 即可。P20

这个观点比较有意思,但是函数模板使用起来会比较麻烦。