文章目录
- 🔢前言
- 🕹️参考资料
- 🕹️实例代码
- 🔢重载机制
- 🥇名称查找
- 🥈模板函数处理
- 🥉重载决议
- END
- 关注我
🔢前言
在C++中函数重载是一个非常强大由复杂的问题。
在C++中本身就有许多函数机制,加上该语言也在不断的迭代,规则是越来越多。
🕹️参考资料
C++ Insights (cppinsights.io)
C++20高级编程 (豆瓣) (douban.com)
第2章 编译时多态
2.1 函数重载机制
函数重载有三个步骤:
- 名称查找
- 模板函数处理
- 重载决议
其中返回值不参与以上函数重载考虑体系。
🕹️实例代码
下方为书中的实例的整理代码。
#include <iostream>
#define LOG() std::cout << "[" << __LINE__ << "]" << __PRETTY_FUNCTION__ << std::endl
namespace animal {
struct Cat {};
void feed(Cat* foo, int) {
LOG();
}
} // namespace animal
struct CatLike {
CatLike(animal::Cat*) {
LOG();
}
};
void feed(CatLike) {
LOG();
}
template <typename T>
void feed(T*, double) {
LOG();
}
template <>
void feed(animal::Cat*, double) {
LOG();
}
int main() {
animal::Cat cat;
feed(&cat, 0);
}
先说结论,本代码的结果为:
[10]void animal::feed(animal::Cat*, int)
🔢重载机制
🥇名称查找
通常分三类:
- 成员函数查找
- 限定名称查找
- 未限定名称查找
成员函数查找
使用
.
,->
进行成员调用时。限定名称查找
使用
::
进行namespace或者class-name时。未限定名称查找
根据参数依赖查找规则**(Argument-Dependent Lookup,简称ADL)** 。
在上述例子中,可以筛选出下面的候选项。
void animal::feed(Cat* foo, int);
void feed(CatLike);
template <typename T> void feed(T*, double);
ADL规则相关
ADL仅在未限定名称查找中作用。
典型应用:
#include <iostream>
int main() {
operator<<(std::cout, "Hello World\n");
}
强制避免ADL
// 套括号,使函数的直接调用变为函数指针的转换
(feed)(&cat, 0);
模板函数相关
名称查找只考虑普通函数和主模板。
模板特化在重载决议部分考虑。
🥈模板函数处理
注意SFINAE不会导致编译失败。
示例中的模板函数通过第一部分,在该阶段展开为:
void feed<animal::Cat>(animal::Cat*, double);
现在还是有三个候选集:
void animal::feed(Cat* foo, int);
void feed(CatLike);
void feed<animal::Cat>(animal::Cat*, double);
🥉重载决议
重载决议分为两步:
- 规约可行函数集
- 挑选最佳可行函数
规约可行函数集
- 如果调用函数有m个实参,那么可行函数必须有m个形参
- 如果候选函数少于m个形参,但最后一个参数是可变参数,则为可行函数。
- 如果候选函数多余m个形参,但从m+1到最后的形参都拥有默认参数,则为可行函数。在挑选最佳可行函数时只考虑前m个形参。
- 从C++20起,如果函数有约束,则必须符合约束。
- 可行需要保证每个形参类型即使通过隐式转换后也能与实参类型对得上。
简单说就是参数个数,类型转换,约束。
经过此轮决议,剩下候选集:
void animal::feed(Cat* foo, int);
void feed<animal::Cat>(animal::Cat*, double);
挑选最佳可行函数
最佳的规则比较多,下面是比较重要几条(并按强度的顺序给出)
- 形参与实参类型最匹配,转换最少。
- 非模板函数优于模板函数。
- 若有多个模板实例,那么最具体的最佳。
- C++20起,若函数有约束,则约束更强的最佳。
下面分别对这几条规则说明
规则1
形参与实参类型最匹配,转换最少。
// [1]
void fun(int) {}
// [2]
void fun(double) {}
int main() {
// [1]
fun(1);
}
规则2
非模板函数优于模板函数。
// [1]
void fun(int) {}
// [2]
template <typename T>
void fun(T) {}
int main() {
// [1]
fun(1);
}
规则3
若有多个模板实例,那么最具体的最佳。
// [1]
template <typename T>
void fun(T) {}
// [2]
template <typename T>
void fun(T*) {}
int main() {
int x;
// [2]
fun(&x);
}
template <typename T>
void fun(T) {}
#ifdef INSIGHTS_USE_TEMPLATE
template <>
void fun<int *>(int *);
#endif
template <typename T>
void fun(T *) {}
/* First instantiated from: insights.cpp:13 */
#ifdef INSIGHTS_USE_TEMPLATE
template <>
void fun<int>(int *) {}
#endif
int main() {
int x;
fun(&x);
return 0;
}
规则4
C++20起,若函数有约束,则约束更强的最佳。
#include <iostream>
#include <type_traits>
// 概念:要求类型 T 必须是整数
template <typename T>
concept Integer = std::is_integral_v<T>;
// 概念:要求类型 T 必须是有符号整型
template <typename T>
concept SignedIntegral = std::is_integral_v<T> && std::is_signed_v<T>;
// [1]
template <Integer T>
void fun(T value) {
std::cout << __LINE__ << std::endl;
}
// [2]
template <SignedIntegral T>
void fun(T value) {
std::cout << __LINE__ << std::endl;
}
int main() {
// [1]
fun(42u);
}
#include <iostream>
#include <type_traits>
template <typename T>
concept Integer = std::is_integral_v<T>;
template <typename T>
concept SignedIntegral = std::is_integral_v<T> && std::is_signed_v<T>;
template <Integer T>
void fun(T value) {
std::cout.operator<<(15).operator<<(std::endl);
}
/* First instantiated from: insights.cpp:26 */
#ifdef INSIGHTS_USE_TEMPLATE
template <>
void fun<unsigned int>(unsigned int value) {
std::cout.operator<<(15).operator<<(std::endl);
}
#endif
template <SignedIntegral T>
void fun(T value) {
std::cout.operator<<(21).operator<<(std::endl);
}
#ifdef INSIGHTS_USE_TEMPLATE
template <>
void fun<unsigned int>(unsigned int value);
#endif
int main() {
fun(42U);
return 0;
}
END
关注我
关注我,学习更多C/C++,算法,计算机知识
B站:
👨💻主页:天赐细莲 bilibili