繁体   English   中英

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

[英]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已经被定义为函数模板的函数参数类型,包括模板参数,所以这里Pvoid (*)(T*) 。)

因此,由于foo是包含函数模板的重载集,因此foovoid (*f)(T*)在模板类型推导中不起作用。 这使得参数T* x和参数"abc"的类型为const char[4] T*不是引用,数组类型衰减为指针类型const char* ,我们发现Tconst 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]intdoubleA 对于(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* ) {}

目标类型为void (*)(std::string) 由于std::string无法匹配T*因此失败。 但是我们发现foo一个重载具有确切的类型void (std::string) ,因此它在load(3)情况下获胜,而overload(3)是可行的。

对于bar重载(4),我们首先尝试对相同的函数模板foo进行类型推导,这次使用目标类型void (*)(const char*)这次类型推导成功, T = const char foo的其他重载都没有确切的类型void (const char*) ,因此使用了函数模板特化,而overload(4)是可行的。

最后,我们通过普通的重载分辨率比较重载(3)和(4)。 在这两种情况下,参数foo到指向函数的指针的转换是精确匹配,因此隐式转换序列都不比另一个好。 但是从const char[4]const char*的标准转换优于从const char[4]std::string的用户定义的转换序列。 所以bar overload(4)是最好的可行函数(它使用void foo<const char>(const char*)作为它的参数)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM