C++泛型编程指南之函数模板优先级匹配

函数的不同修饰

![[Pasted image 20240810175052.png]]

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

对于一个普通函数而言,如果对参数和返回类型修饰存在如下形式:

  1. int max(int, int);
  2. int max(const int*, const int*);
  3. int max(int&, int&);
  4. int max(const int, const int);
  5. int max(const int* const, const int* const);
  6. int max(const int&, const int&);
  7. int& max(int&, int&); (确保不返回临时引用,不安全)
  8. int* max(int*, int*); (确保指针非空)
  9. const int* max(const int*, const int*); (确保指针非空)
  10. int& max(const int&, const int&); (确保不返回临时引用,不安全)
  11. int max(const int&&, const int&&); (万能引用)

不要返回临时对象的引用,不要传入和返回空指针

  1. 不要返回临时对象的引用:在某些编程语言中,如C++,临时对象是那些在表达式求值后不再需要的对象,它们通常在表达式结束时被销毁。返回对这些临时对象的引用可能会导致悬挂引用(dangling reference),因为引用的对象可能已经不存在了。
  1. 不要传入和返回空指针:在许多编程语言中,空指针是一个不指向任何有效内存地址的指针。使用空指针可能会导致程序崩溃或未定义行为。因此,避免传递空指针给函数,以及从函数返回空指针是一个不安全的做法。

模板函数的不同修饰

对于一个函数模板而言,对参数和返回值类型进行修饰也是相似的,仅仅是将 特别指定的 类型 int 泛化为一个 模板参数类型 typename T

  1. int max(int, int);
template<typename T>
T max(T a, T b);
  1. int max(const int*, const int*);
template<typename T>
T max(const T* a, const T* b);
  1. int max(int&, int&);
template<typename T>
T max(T& a, T& b);
  1. int max(const int, const int);
template<typename T>
T max(const T a, const T b);
  1. int max(const int* const, const int* const);
template<typename T>
T max(const T* const a, const T* const b);
  1. int max(const int&, const int&);
template<typename T>
T max(const T& a, const T& b);
  1. int& max(int&, int&);
template<typename T>
T& max(T& a, T& b);
  1. int* max(int*, int*);
template<typename T>
T* max(T* a, T* b);
  1. const int* max(const int*, const int*);
template<typename T>
const T* max(const T* a, const T* b);
  1. int& max(const int&, const int&);
template<typename T>
T& max(const T& a, const T& b);
  1. int max(const int&&, const int&&); (万能引用)
template<typename T>
T max(T&& a, T&& b);

修饰带来的功能上的变化

对于在原有函数上面的修饰,会影响他们的功能,比如:

原始版本

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

    • 不同: 与原始版本相同,直接处理 int 类型的值。
  2. int max(const int*, const int*);

    • 不同: 处理指向 int 类型的指针,适用于处理数组或动态分配的内存中的元素。
  3. int max(int&, int&);

    • 不同: 处理 int 类型的引用,避免了不必要的值拷贝,适用于已存在的变量。
  4. int max(const int, const int);

    • 不同: 处理 int 类型的常量值,表明参数不可修改。
  5. int max(const int* const, const int* const);

    • 不同: 处理指向 int 类型的常量指针,表明指针本身和指针所指向的数据均不可修改。
  6. int max(const int&, const int&);

    • 不同: 处理 int 类型的常量引用,避免了不必要的值拷贝,同时表明参数不可修改。
  7. int& max(int&, int&);

    • 不同: 返回一个引用,适用于需要引用返回值的情况,但需要注意不要返回局部变量的引用。
  8. int* max(int*, int*);

    • 不同: 返回一个指向较大值的指针,适用于需要指针作为返回值的情况。
  9. const int* max(const int*, const int*);

    • 不同: 返回一个指向较大值的常量指针,适用于需要常量指针作为返回值的情况。
  10. int& max(const int&, const int&);

  • 不同: 返回一个常量引用,适用于需要引用返回值的情况,同样需要注意不要返回局部变量的引用。
  1. int max(const int&&, const int&&); (万能引用)
  • 不同: 处理万能引用,可以接受任何类型的左值或右值引用,支持完美转发。

修饰带来的函数调用,模板实例化上的变化 (函数/模板的重载决议)

普通函数的不同修饰不仅影响函数的功能,还会影响函数调用时的选择和匹配。当我们使用不同的参数类型、返回类型或者修饰符时,这些变化会影响函数重载机制下的选择过程。

对于模板函数来说,不同的修饰符也会影响函数调用的选择和匹配。模板函数的参数类型、返回类型以及参数修饰符等都会影响模板实例化的过程以及最终选择哪个模板实例。

我们将列举一些常见情况,介绍这种决议不同。

非模板类型(函数) 匹配程度的排序

  1. 完美匹配 (1)
    • 参数具有表达式的类型,或者具有指向表达式类型的引用类型(可能添加了 const 和/或 volatile 限定符)。
    • 这种情况下不需要任何转换。
   void perfectMatch(int x);  // 函数声明
   void perfectMatch(const int& x);  // 函数声明

   int main() {
       int y = 10;
       perfectMatch(y);  // 完美匹配 int x
       perfectMatch(y);  // 完美匹配 const int& x
       return 0;
   }
  1. 匹配需要微调 (2)

    • 将数组变量衰减为指向其第一个元素的指针。
    • const 添加以匹配 int* 类型的参数和 const int* 类型的参数。
    • 这些微调并不涉及类型转换,而是指针和数组的特殊规则。

    示例代码:

    void decayArray(int arr[]);  // 函数声明
    void addConst(const int* p);  // 函数声明
    
    int main() {
        int array[5] = {1, 2, 3, 4, 5};
        decayArray(array);  // 数组衰减为指针
        int* ptr = &array;  // 指向数组的指针
        addConst(ptr);  // 添加 const
        return 0;
    }
    
  2. 类型提升匹配 (3)

    • 类型提升是一种隐式转换,包括:
      • 将小整型(如 bool, char, short, 有时还包括 enum)转换为 intunsigned intlongunsigned long
      • float 转换为 double

示例代码:

void typePromotion(short s, float f);  // 函数声明

int main() {
    short smallInt = 10;
    float floatVal = 10.0f;
    typePromotion(smallInt, floatVal);  // 类型提升
    return 0;
}
  1. 标准转换匹配 (4)
    • 任何类型的标准转换(如 intfloat),或从派生类到其公共、显式基类的转换。
    • 不包括对转换运算符或转换构造函数的隐式调用。(对函数模板不支持这个转换构造函数带来转换的影响)

示例代码:

class Base {};
class Derived : public Base {};

void standardConversion(int i, Base* b);  // 函数声明

int main() {
    int intValue = 10;
    Derived derivedObj;
    standardConversion(intValue, &derivedObj);  // 标准转换
    return 0;
}
  1. 用户定义的转换匹配 (5)
    • 允许任何类型的隐式转换,包括通过构造函数或转换运算符进行的转换。

示例代码:

class MyString {
public:
    MyString(const char* str) {}
};

void userDefinedConversion(MyString ms);  // 函数声明

int main() {
    const char* cStr = "Hello";
    userDefinedConversion(cStr);  // 用户定义的转换
    return 0;
}
  1. 省略号匹配 (6)
    • 省略号参数几乎可以匹配任何类型,但有一个例外:具有重要复制构造函数的类型可能是有效的,也可能不是有效的(实现可以允许或禁止)。

示例代码:

void variadicArgs(...);  // 函数声明

int main() {
    int someInt = 10;
    variadicArgs(someInt);  // 省略号匹配
    return 0;
}

总结

  • 完美匹配 (1):参数类型与函数参数类型完全相同,或者参数是左值引用且指向相同类型的对象。
  • 匹配需要微调 (2):数组变量衰减为指针,或添加 const 限定符以匹配参数类型。
  • 类型提升匹配 (3):小整型和浮点型的自动提升。
  • 标准转换匹配 (4):标准转换或派生类到基类的转换。
  • 用户定义的转换匹配 (5):通过构造函数或转换运算符进行的转换。
  • 省略号匹配 (6):省略号参数可以匹配多种类型,但对于某些类型可能有限制。

当存在多个重载函数 并且都符合匹配条件时, 会优先调用完美匹配的函数,其次是匹配微调,再其次是类型提升匹配,,,,最后是用户自定义的转换匹配。

对于函数模板的匹配规则也是基本遵循上面的匹配顺序,,但是有一些额外的考虑因素(之后会详细讲):

  1. 完美匹配: 如果模板实例化后可以产生完美匹配,则优先选择该模板。
  2. 最简单模板实例化原则 (Most Viable Function, MVF): 如果存在多个可能的模板实例化版本,编译器会选择最简单的一个,即需要最少转换的那个。
  3. 模板特化优先: 如果有显式的模板特化与给定的实参匹配,那么特化版本将优先于普通模板实例。

如果存在多个模板实例化版本具有相同的复杂度,那么编译器将无法决定使用哪一个,这会导致ambiguous overload错误。

本节主要介绍了

  1. 函数不同的参数修饰和返回值修饰带来的影响
  2. 函数/函数模板类型推导的优先级别

下一节我们将深入 函数模板的重载决议



查看普通函数的实现

原始版本

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

这是一个简单的函数,接受两个 int 类型的参数,并返回较大的一个。

使用指针

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

这个版本接受两个指向 int 的常量指针,并返回它们所指向的值中的较大者。注意,我们需要解引用这些指针以获取实际的值进行比较。

使用引用

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

这个版本接受两个 int 类型的引用作为参数。由于引用的行为类似于别名,所以这个版本和原始版本非常相似。

使用常量

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

这个版本接受两个常量 int 类型的参数。实际上,由于 int 类型的值传递是值拷贝,这里的 const 关键字并没有太大意义,除非是在模板上下文中或用于某些编译器优化。

使用常量指针

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

这个版本接受两个指向常量 int 的常量指针。这里的 const 有两个含义:首先,指针所指向的数据是不可更改的;其次,指针本身也是不可重新赋值的。

使用常量引用

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

这个版本接受两个 int 类型的常量引用。与普通的引用版本类似,但是参数是不可更改的。

返回引用

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

这个版本返回一个引用,但需要注意的是,如果返回的是局部变量的引用,则会导致未定义行为。为了使这个版本有意义,通常需要使用外部变量或成员变量。

返回指针

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

这个版本返回指向较大值的指针。注意,这里返回的是原始指针,如果 ab 不是指向实际数据的指针,那么这个返回可能会有问题。

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

这个版本返回一个指向较大值的常量指针。这里返回的是指向较大值的原始指针。

返回常量引用

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

这个版本返回一个常量引用,与返回引用的情况类似,需要注意返回的引用必须是有效的。

查看泛化函数的实现

对于这些不同类型的 max 函数变体,我们可以尝试将其泛化到模板函数中,以便支持多种类型而不必为每种类型编写单独的函数。下面是基于上述变体的模板函数示例。

1. int max(int, int);
template<typename T>
T max(T a, T b) {
    return b < a ? a : b;
}
2. int max(const int*, const int*);
template<typename T>
T max(const T* a, const T* b) {
    return (*b < *a) ? *a : *b;
}
3. int max(int&, int&);
template<typename T>
T max(T& a, T& b) {
    return b < a ? a : b;
}
4. int max(const int, const int);
template<typename T>
T max(const T a, const T b) {
    return b < a ? a : b;
}
5. int max(const int* const, const int* const);
template<typename T>
T max(const T* const a, const T* const b) {
    return (*b < *a) ? *a : *b;
}
6. int max(const int&, const int&);
template<typename T>
T max(const T& a, const T& b) {
    return b < a ? a : b;
}
7. int& max(int&, int&);
template<typename T>
T& max(T& a, T& b) {
    return b < a ? a : b;
}
8. int* max(int*, int*);
template<typename T>
T* max(T* a, T* b) {
    return b < a ? a : b;
}
9. const int* max(const int*, const int*);
template<typename T>
const T* max(const T* a, const T* b) {
    return (*b < *a) ? a : b;
}
10. int& max(const int&, const int&);
template<typename T>
T& max(const T& a, const T& b) {
    return b < a ? a : b;
}

注意事项

  • 在第7个和第10个变体中返回引用时,需要注意不要返回局部变量的引用,因为这会导致未定义行为。为了避免这个问题,通常的做法是返回一个外部变量的引用,或者返回一个值。
  • 第8个和第9个变体中,返回指针时需要确保指针非空,即指针所指向的数据是有效的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁金金

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值