简体   繁体   English

当参数是一个重载函数时,重载解析如何工作?

[英]How does overload resolution work when an argument is an overloaded function?

Preamble 前言

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 . 这个问题是一个介绍性问题,然后是一个更复杂的问题

Question

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? 必须对他们对重载决策的理解做出哪些修改,以便它还包括使用重载函数作为参数的情况?

Examples 例子

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)

Code to play with: 代码:

#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已经被定义为函数模板的函数参数类型,包括模板参数,所以这里Pvoid (*)(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是包含函数模板的重载集,因此foovoid (*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* ,我们发现Tconst 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]intdoubleA 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() and g() are both overloaded functions, the cross product of possibilities must be considered to resolve f(&g) , or the equivalent expression f(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.

相关问题 当重载函数作为参数参与时,模板参数推导如何工作? - How does template argument deduction work when an overloaded function is involved as an argument? 重载解析如何与可变参数函数一起使用? - How does overload resolution work with variadic functions? 使用nullptr作为参数的函数重载解析 - Function overload resolution with nullptr as argument 我可以重载一个重载的运算符吗? 操作员重载如何工作 - Can I overload an overloaded operator? How does Operator Overloading work 将std :: initializer_list与布尔重载函数一起使用时,具有重载分辨率的意外行为 - Unexpected behaviour with overload resolution when using std::initializer_list with a boolean overloaded function 此模板类型推导和重载解析如何工作? - How does this template type deduction and overload resolution work? 重载解析如何在private修饰符的上下文中起作用? - How does overload resolution work in the context of private modifier? 在新的初始化序列存在的情况下,运算符重载解析如何工作? - How does operator overload resolution work in the presence of the new initializer sequence? 重载解析如何适用于std :: vector <int> :: insert - How does overload resolution work for std::vector<int>::insert 运算符如何在名称空间中重载解析? - How does the operator overload resolution work within namespaces?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM