您可能已经想通了,因为您发布这个问题已经快 3 年了。但如果你没有,我会给出答案。
有趣的是第一个fun的模板参数F被解析为double (double)的纯函数类型,而第二个fun的模板参数F被解析为当 fun 仅使用函数名调用时,更期望的 double (*)(double) 函数指针类型。
首先,请记住数组和函数很奇怪,因为数组可以隐式衰减为指向其第一个元素的指针,而函数可以隐式衰减为函数指针。 虽然在语法上有效,但函数参数实际上不能是数组或函数类型,而是指针,这意味着函数参数可以用数组或函数的类型编写,但编译器将此类类型视为指针。例如看下面的代码:
int val [3]; //type of val is 'int [3]'
int * pval = val; //type of pval is 'int *'
//the assignment is correct since val can decay into 'int *'
double foo(double); //type of foo is 'double (double)'
double (*pfoo) (double); // type of pfoo is 'double (*)(double)'
pfoo = foo; //correct since functions can decay into function pointers.
void bar(int x []); // syntax is correct
// but compilers see the type of x as 'int *'
void bar(int x(int));// again syntax is correct
// but compilers see the type of x as 'int (*)(int)'
然而,当函数参数具有引用类型时,事情就变得更加奇怪了。具有对数组/函数的引用类型的函数参数被认为具有对数组/函数的引用类型,不是指针类型。例如:
void bar(int (& x)[2]); //type of x is now 'int (&) [2]'
void bar(int (& x)(int)); //type of x is now 'int (&)(int)'
关于您的第一个问题,由于您的第一个函数(fun(const F& f))中的参数类型包含引用,因此在传递函数时,f 的类型将被推断为对函数的引用作为论据;更准确地说,f 的推断类型将是double (&) (double)。另一方面,由于第二个函数的参数类型不包含引用(fun(F f)),编译器隐含地将f 的类型推断为函数指针(f 的推断类型将是double (*)(double)),当函数作为参数传递时。
但是,当我们将 fun(Test) 的调用更改为 fun(&Test) 以显式获取函数的地址(或显式函数指针)时,两者都将模板参数 F 解析为 double (*)(double)!
好吧,既然您明确地将函数指针类型作为参数传递(通过获取Test 的地址),f 的推导类型必须有一个指针。 但是,第一个函数的参数的引用和恒定性并没有被忽略。运行fun(&Test)时,第一个函数f的推导类型为double (* const &) (double),第二个函数f的推导类型为double (*) (double)。
PS:如果我们添加另一个fun 的实例来获取F* f,那么似乎重载规则只是决定选择这个版本,并且根本不会报告任何歧义(尽管,正如我已经说过的,模棱两可不是前面最关心的问题,但在最后一种情况下,我确实想知道为什么第三个版本是这里的最佳匹配?)
(我删除了我之前对该部分的回答,请参考下文)
编辑:
对于添加第三个函数(fun(F * f))时如何不再有歧义的问题,我给出了一个非常草率的答案。我希望下面是一个明确的答案。
在函数模板的情况下,决定选择哪个函数的规则是首先找出给定参数的模板特化集。这样做的原因是为了消除导致替换失败的函数模板作为候选。然后基于从参数到参数的转换,从非模板函数的候选池和有效的模板特化中消除更差的匹配。如果一个非模板和一个模板函数同样匹配,那么非模板就会被选中。如果多个模板函数同样匹配,则使用partial ordering rules 来消除不太专业的函数模板。如果一个作为最专业的功能模板闪耀,那么它就解决了;另一方面,如果两者都不是更专业的,则编译器会发出歧义错误。不用说,如果没有找到有效的候选人,就会再次发出错误。
现在让我们再次指出参数Test 的模板特化。如上所述,在模板类型推导后,第一个函数模板的模板特化为void fun(double (&f) (double) ),第二个函数模板的模板特化为void fun(double (*f) (double) )。根据从参数类型double (double) 到候选模板函数的参数类型double (&) (double) 和double (*) (double) 所需的转换,它们都被认为是完全匹配的,因为只需要微不足道的转换。因此,部分排序规则被用来区分哪一个更专业。事实证明两者都不是,所以发出了歧义错误。
当您添加第三个函数模板 (void fun(F * f)) 时,模板类型推导会生成模板特化为 void fun(double (*f)(double)。和之前一样,所有三个模板函数都是同样好的匹配(实际上它们是完全匹配的)。再次因为这个原因,部分排序规则被用作最后的手段,结果证明第三个函数模板更专业,因此被选中。
关于琐碎转换的注意事项:虽然不完整,但以下从参数类型到参数类型的转换被认为是琐碎转换(给定类型T):
- 从
T 到const T
- 从
T 到T &
- 或从数组或函数类型到它们对应的指针类型(开头提到的衰减)。
EDIT #2:看来我可能没有使用正确的措辞,所以为了清楚我的意思是 function template 是一个创建函数的模板模板函数是由模板创建的函数。