当参数是重载函数时,重载解析是如何工作的
How does overload resolution work when an argument is an overloaded function?
序言
C++中的重载解析可能是一个过于复杂的过程。要理解所有控制重载解析的C++规则,需要花费大量的脑力劳动。最近我突然想到,参数列表中出现重载函数的名称会增加重载解析的复杂性。由于这恰好是一个广泛使用的案例,我发布了一个问题并得到了一个答案,这让我更好地理解了这个过程的机制。然而,在iostreams的背景下对这个问题的表述似乎在一定程度上分散了答案的焦点,使其偏离了所解决问题的本质。因此,我开始深入研究,并提出了其他例子,要求对这个问题进行更详细的分析。这个问题是一个介绍性的问题,后面是一个更复杂的问题。
问题
假设完全理解在没有本身就是重载函数名称的参数的情况下重载解析是如何工作的。必须对他们对重载解析的理解进行哪些修改,以便它也涵盖将重载函数用作自变量的情况?
示例
鉴于这些声明:
void foo(int) {}
void foo(double) {}
void foo(std::string) {}
template<class T> void foo(T* ) {}
struct A {
A(void (*)(int)) {}
};
void bar(int x, void (*f)(int)) {}
void bar(double x, void (*f)(double)) {}
void bar(std::string x, void (*f)(std::string)) {}
template<class T> void bar(T* x, void (*f)(T*)) {}
void bar(A x, void (*f2)(double)) {}
以下表达式导致名称foo
的以下解析(至少对于gcc 5.4(:
bar(1, foo); // foo(int)
// but if foo(int) is removed, foo(double) takes over
bar(1.0, foo); // foo(double)
// but if foo(double) is removed, foo(int) takes over
int i;
bar(&i, foo); // foo<int>(int*)
bar("abc", foo); // foo<const char>(const char*)
// but if foo<T>(T*) is removed, foo(std::string) takes over
bar(std::string("abc"), foo); // foo(std::string)
bar(foo, foo); // 1st argument is foo(int), 2nd one - foo(double)
要玩的代码:
#include <iostream>
#include <string>
#define PRINT_FUNC std::cout << "t" << __PRETTY_FUNCTION__ << "n";
void foo(int) { PRINT_FUNC; }
void foo(double) { PRINT_FUNC; }
void foo(std::string) { PRINT_FUNC; }
template<class T> void foo(T* ) { PRINT_FUNC; }
struct A { A(void (*f)(int)){ f(0); } };
void bar(int x, void (*f)(int) ) { f(x); }
void bar(double x, void (*f)(double) ) { f(x); }
void bar(std::string x, void (*f)(std::string)) { f(x); }
template<class T> void bar(T* x, void (*f)(T*)) { f(x); }
void bar(A, void (*f)(double)) { f(0); }
#define CHECK(X) std::cout << #X ":n"; X; std::cout << "n";
int main()
{
int i = 0;
CHECK( bar(i, foo) );
CHECK( bar(1.0, foo) );
CHECK( bar(1.0f, foo) );
CHECK( bar(&i, foo) );
CHECK( bar("abc", foo) );
CHECK( bar(std::string("abc"), foo) );
CHECK( bar(foo, foo) );
}
让我们来看看最有趣的案例
bar("abc", foo);
要弄清楚的主要问题是,使用哪个bar
过载。和往常一样,我们首先通过名称查找获得一组重载,然后对重载集中的每个函数模板进行模板类型推导,然后进行重载解析。
这里真正有趣的部分是声明的模板类型推导
template<class T> void bar(T* x, void (*f)(T*)) {}
本标准在14.8.2.1/6:中有这样的表述
当
P
是函数类型、指向函数类型的指针或指向成员函数类型的指示器时:
如果参数是包含一个或多个函数模板的重载集,则该参数将被视为非推导上下文。
如果参数是重载集(不包含函数模板(,则会尝试使用该集的每个成员进行试参数推导。如果只有一个重载集成员的推导成功,则该成员将用作推导的参数值。如果重载集的多个成员的推导成功,则参数将被视为非推导上下文。
(P
已经被定义为函数模板的函数参数类型,包括模板参数,所以这里P
是void (*)(T*)
。(
因此,由于foo
是一个包含函数模板的重载集,foo
和void (*f)(T*)
在模板类型推导中不起作用。这使得参数T* x
和参数"abc"
的类型为const char[4]
。T*
不是引用,数组类型衰减为指针类型const char*
,我们发现T
是const char
。
现在我们有了这些候选者的过载解决方案:
void bar(int x, void (*f)(int)) {} // (1)
void bar(double x, void (*f)(double)) {} // (2)
void bar(std::string x, void (*f)(std::string)) {} // (3)
void bar<const char>(const char* x, void (*f)(const char*)) {} // (4)
void bar(A x, void (*f2)(double)) {} // (5)
是时候找出其中哪些是可行的功能了。(1( 、(2(和(5(是不可行的,因为没有从const char[4]
到int
、double
或A
的转换。对于(3(和(4(,我们需要弄清楚foo
是否是有效的第二个自变量。标准第13.4/1-6节:
在某些上下文中,使用不带参数的重载函数名会解析为重载集中特定函数的函数、指向函数的指针或指向成员函数的指针。函数模板名称被认为是在这种上下文中命名一组重载函数。所选函数的类型与上下文中所需目标类型的函数类型相同。目标可以是
- 函数(5.2.2(的参数
如果名称是函数模板,则完成模板参数推导(14.8.2.2(,如果参数推导成功,则生成的模板参数列表将用于生成单个函数模板专用化,该专用化将添加到所考虑的重载函数集中。。。
[注意:如果
f()
和g()
都是重载函数,则必须考虑可能性的叉积来解析f(&g)
或等效表达式f(g)
。-结束注释]
对于bar
的过载(3(,我们首先尝试的类型推导
template<class T> void foo(T* ) {}
目标类型为CCD_ 27。由于std::string
无法与T*
匹配,因此此操作失败。但我们发现foo
的一个过载具有确切的类型void (std::string)
,因此它在过载(3(的情况下获胜,并且过载(2(是可行的。
对于bar
的重载(4(,我们首先尝试对相同的函数模板foo
进行类型推导,这次是目标类型void (*)(const char*)
。这次类型推导成功,T
=const char
。foo
的其他重载都没有确切的类型void (const char*)
,因此使用了函数模板专门化,重载(4(是可行的。
最后,我们通过普通过载分辨率比较过载(3(和(4(。在这两种情况下,参数foo
到函数指针的转换都是精确匹配,因此两个隐式转换序列都不如另一个。但从const char[4]
到const char*
的标准转换比从const char[4]
到std::string
的自定义转换序列要好。因此,bar
的重载(4(是最佳可行函数(并且它使用void foo<const char>(const char*)
作为其自变量(。
- C++编程:运算符重载中的引用如何工作?
- C++,()运算符重载,它的工作是什么
- 为什么重载运算符"="动态数组的类上无法正常工作?C++
- 如何使'='重载在'='的另一边工作
- C++ 继承:基类中重载 operator+ 的 2 次在派生类中无法正常工作
- ostream_iterator运算符=在pair<int,int>上失败,但在包装器类上工作。我不能为成对<>重载运算符<<吗?
- 当传递NULL作为参数时,函数重载是如何工作的
- c++ 中的函数重载如何在没有钻石继承的情况下工作?
- 工作函数重载如何使用可变参数解析?
- 重载按位 OR('|'),用于链接操作无法按预期工作
- 此模板类型推断和重载解析如何工作
- 当其中一个函数未编译时,函数重载查找如何工作?
- 运算符重载在 CPP 中的排序中如何工作?
- 我的重载后增量方法无法按预期工作。为什么?
- 运算符重载如何工作,为什么在我的情况下不起作用?
- 运算符重载 =, *=.如何使这项工作
- 在存在新的初始值设定项序列的情况下,运算符重载解析如何工作
- 重载输入/输出运算符,为什么它以这种方式工作而不是以另一种方式工作
- C++ 重载 ->运算符,它是如何工作的?
- 函数重载的工作原理