[英]How does overload resolution work when an argument is an overloaded function?
Overload resolution in C++ can be an overly complex process. C ++中的过载分辨率可能是一个过于复杂的过程。 It takes quite a lot of mental effort to understand all of the C++ rules that govern overload resolution. 理解控制重载决策的所有C ++规则需要花费大量精力。 Recently it occurred to me that the presence of the name of an overloaded function in the argument list can add to the complexity of overload resolution. 最近我发现在参数列表中存在重载函数的名称会增加重载决策的复杂性。 Since it happened to be a widely used case, I posted a question and received an answer that allowed me to better understand the mechanics of that process. 由于它恰好是一个广泛使用的案例,我发布了一个问题,并得到了一个答案,使我能够更好地理解该过程的机制。 However, the formulation of that question in the context of iostreams seems to have somewhat distracted the focus of the answers from the very essence of the problem being addressed. 然而,在iostreams的背景下提出这个问题似乎有点分散了答案的焦点,从正在解决的问题的本质。 So I started delving deeper and came up with other examples that ask for more elaborate analysis of the issue. 所以我开始深入研究并提出了其他要求对问题进行更详细分析的例子。 This question is an introductory one and is followed by a more sophisticated one . 这个问题是一个介绍性问题,然后是一个更复杂的问题 。
Assume that one fully understands how overload resolution works in the absence of arguments that are themselves names of overloaded functions. 假设一个人完全理解在没有自身名称的重载函数的情况下重载解析如何工作。 What amendments must be made to their understanding of overload resolution, so that it also covers cases where overloaded functions are used as arguments? 必须对他们对重载决策的理解做出哪些修改,以便它还包括使用重载函数作为参数的情况?
Given these declarations: 鉴于这些声明:
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)) {}
Below expressions result in the following resolution of the name foo
(at least with gcc 5.4): 下面的表达式导致名称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) );
}
Let's take the most interesting case, 让我们来看看最有趣的案例,
bar("abc", foo);
The primary question to figure out is, which overload of bar
to use. 的首要问题搞清楚的是,其过载的bar
使用。 As always, we first get a set of overloads by name lookup, then do template type deduction for each function template in the overload set, then do overload resolution. 与往常一样,我们首先通过名称查找获得一组重载,然后对重载集中的每个函数模板进行模板类型推导,然后执行重载解析。
The really interesting part here is the template type deduction for the declaration 这里真正有趣的部分是声明的模板类型推导
template<class T> void bar(T* x, void (*f)(T*)) {}
The Standard has this to say in 14.8.2.1/6: 标准在14.8.2.1/6中有这样的说法:
When
P
is a function type, pointer to function type, or pointer to member function type: 当P
是函数类型时,指向函数类型的指针或指向成员函数类型的指针:
If the argument is an overload set containing one or more function templates, the parameter is treated as a non-deduced context. 如果参数是包含一个或多个函数模板的重载集,则该参数将被视为非推导的上下文。
If the argument is an overload set (not containing function templates), trial argument deduction is attempted using each of the members of the set. 如果参数是重载集(不包含函数模板),则尝试使用集合中的每个成员进行试验参数推导。 If deduction succeeds for only one of the overload set members, that member is used as the argument value for the deduction. 如果仅对其中一个重载集成员进行推导成功,则该成员将用作推导的参数值。 If deduction succeeds for more than one member of the overload set the parameter is treated as a non-deduced context. 如果对重载集的多个成员进行推导成功,则将该参数视为非推导上下文。
( P
has already been defined as the function template's function parameter type including template parameters, so here P
is void (*)(T*)
.) ( P
已经被定义为函数模板的函数参数类型,包括模板参数,所以这里P
是void (*)(T*)
。)
So since foo
is an overload set containing a function template, foo
and void (*f)(T*)
don't play a role in template type deduction. 因此,由于foo
是包含函数模板的重载集,因此foo
和void (*f)(T*)
在模板类型推导中不起作用。 That leaves parameter T* x
and argument "abc"
with type const char[4]
. 这使得参数T* x
和参数"abc"
的类型为const char[4]
。 T*
not being a reference, the array type decays to a pointer type const char*
and we find that T
is const char
. T*
不是引用,数组类型衰减为指针类型const char*
,我们发现T
是const char
。
Now we have overload resolution with these candidates: 现在我们对这些候选人进行了重载解析:
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)
Time to find out which of these are viable functions. 是时候找出哪些是可行的功能了。 (1), (2), and (5) are not viable because there is no conversion from const char[4]
to int
, double
, or A
. (1),(2)和(5)不可行,因为没有从const char[4]
到int
, double
或A
。 For (3) and (4) we need to figure out if foo
is a valid second argument. 对于(3)和(4),我们需要弄清楚foo
是否是有效的第二个参数。 In Standard section 13.4/1-6: 在标准第13.4 / 1-6节中:
A use of an overloaded function name without arguments is resolved in certain contexts to a function, a pointer to function or a pointer to member function for a specific function from the overload set. 在某些上下文中,使用不带参数的重载函数名称解析为函数,指向函数的指针或指向来自重载集的特定函数的成员函数的指针。 A function template name is considered to name a set of overloaded functions in such contexts. 函数模板名称被认为是在这种上下文中命名一组重载函数。 The function selected is the one whose type is identical to the function type of the target type required in the context. 选择的函数是其类型与上下文中所需的目标类型的函数类型相同的函数。 The target can be 目标可以是
- ... ...
- a parameter of a function (5.2.2), 函数的参数(5.2.2),
- ... ...
... If the name is a function template, template argument deduction is done (14.8.2.2), and if the argument deduction succeeds, the resulting template argument list is used to generate a single function template specialization, which is added to the set of overloaded functions considered. ...如果名称是函数模板,则完成模板参数推导(14.8.2.2),如果参数推导成功,则生成的模板参数列表用于生成单个函数模板特化,并将其添加到集合中考虑重载函数。 ... ...
[ Note: If
f()
andg()
are both overloaded functions, the cross product of possibilities must be considered to resolvef(&g)
, or the equivalent expressionf(g)
. [ 注意:如果f()
和g()
都是重载函数,则必须考虑可能性的叉积来解析f(&g)
或等价表达式f(g)
。 - end note ] - 结束说明 ]
For overload (3) of bar
, we first attempt type deduction for 对于bar
过载(3),我们首先尝试类型推导
template<class T> void foo(T* ) {}
with target type void (*)(std::string)
. 目标类型为void (*)(std::string)
。 This fails since std::string
cannot match T*
. 由于std::string
无法匹配T*
因此失败。 But we find one overload of foo
which has the exact type void (std::string)
, so it wins for the overload (3) case, and overload (3) is viable. 但是我们发现foo
一个重载具有确切的类型void (std::string)
,因此它在load(3)情况下获胜,而overload(3)是可行的。
For overload (4) of bar
, we first attempt type deduction for the same function template foo
, this time with target type void (*)(const char*)
This time type deduction succeeds, with T
= const char
. 对于bar
重载(4),我们首先尝试对相同的函数模板foo
进行类型推导,这次使用目标类型void (*)(const char*)
这次类型推导成功, T
= const char
。 None of the other overloads of foo
have the exact type void (const char*)
, so the function template specialization is used, and overload (4) is viable. foo
的其他重载都没有确切的类型void (const char*)
,因此使用了函数模板特化,而overload(4)是可行的。
Finally, we compare overloads (3) and (4) by ordinary overload resolution. 最后,我们通过普通的重载分辨率比较重载(3)和(4)。 In both cases, the conversion of argument foo
to a pointer to function is an Exact Match, so neither implicit conversion sequence is better than the other. 在这两种情况下,参数foo
到指向函数的指针的转换是精确匹配,因此隐式转换序列都不比另一个好。 But the standard conversion from const char[4]
to const char*
is better than the user-defined conversion sequence from const char[4]
to std::string
. 但是从const char[4]
到const char*
的标准转换优于从const char[4]
到std::string
的用户定义的转换序列。 So overload (4) of bar
is the best viable function (and it uses void foo<const char>(const char*)
as its argument). 所以bar
overload(4)是最好的可行函数(它使用void foo<const char>(const char*)
作为它的参数)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.