当参数是重载函数时,重载解析如何工作? [英] How does overload resolution work when an argument is an overloaded function?

查看:57
本文介绍了当参数是重载函数时,重载解析如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

序言

C ++中的重载解析可能是一个过于复杂的过程.要了解所有用于控制重载解析的C ++规则,需要花费大量的精力.最近,我想到参数列表中存在重载函数的名称会增加重载解析的复杂性.由于它碰巧是一个被广泛使用的案例,因此我发布了一个问题并收到了答案这使我可以更好地了解该过程的机制.但是,在iostream的背景下提出该问题似乎已经从解决问题的本质上分散了答案的重点.因此,我开始更深入地研究,并提出了其他示例,要求对问题进行更详尽的分析.这个问题是一个介绍性问题,其后是一个


要使用的代码:

  #include< iostream>#include< string>#define PRINT_FUNC std :: cout<<"\ t"<<__PRETTY_FUNCTION__<<"\ n";无效foo(int){PRINT_FUNC;}无效foo(double){PRINT_FUNC;}无效foo(std :: string){PRINT_FUNC;}模板< T类>无效foo(T *){PRINT_FUNC;}结构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);}模板< 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节中:

在某些情况下,对不带参数的重载函数名的使用将被解析为函数,重载集中特定函数的函数指针或成员函数指针.在这种情况下,功能模板名称被认为是对一组重载功能的命名.所选函数的类型与上下文中所需的目标类型的函数类型相同.目标可以是

...如果名称是函数模板,则完成模板参数推导(14.8.2.2),并且如果参数推导成功,则使用生成的模板参数列表来生成单个函数模板特化,并将其添加到所考虑的重载函数集....

[注意:如果 f() g()都是重载函数,则必须考虑可能性的叉积来解决 f(& g)或等效表达式 f(g).-尾注]

对于 bar 的重载(3),我们首先尝试对类型进行推导

  template< class T>无效的foo(T *){} 

目标类型为 void(*)(std :: string).这失败了,因为 std :: string 无法匹配 T * .但是我们发现 foo 的一个重载具有确切的类型 void(std :: string),因此在重载(3)和重载(3)的情况下胜出.是可行的.

对于 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 *)作为其参数).

Preamble

Overload resolution in C++ can be an overly complex process. It takes quite a lot of mental effort to understand all of the C++ rules that govern overload resolution. 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. 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):

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. 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:

When P is a function type, pointer to function type, or pointer to member function type:

  • 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*).)

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. That leaves parameter T* x and argument "abc" with type 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.

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. For (3) and (4) we need to figure out if foo is a valid second argument. In Standard section 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),
  • ...

... 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. ...

[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). - end note]

For overload (3) of bar, we first attempt type deduction for

template<class T> void foo(T* ) {}

with target type void (*)(std::string). This fails since std::string cannot match 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.

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. 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.

Finally, we compare overloads (3) and (4) by ordinary overload resolution. 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. 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. So overload (4) of bar is the best viable function (and it uses void foo<const char>(const char*) as its argument).

这篇关于当参数是重载函数时,重载解析如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆