使用时构建以下代码
clang -Wall main.cpp -o main.o
生成以下诊断(在代码之后):
template <typename F>
void fun(const F& f)
{
}
template <typename F>
void fun(F f)
{
}
double Test(double d) { return d; }
int main(int argc, const char * argv[])
{
fun(Test);
return 0;
}
诊断:
main.cpp:17:5: error: call to 'fun' is ambiguous
fun(Test);
^~~
main.cpp:2:6: note: candidate function [with F = double (double)]
void fun(const F& f)
^
main.cpp:8:6: note: candidate function [with F = double (*)(double)]
void fun(F f)
^
1 error generated.
有趣的部分不是模糊性错误本身(这不是主要关注点) . 有趣的是,第一个 fun
的模板参数 F
被解析为 double (double)
的纯函数类型,而第二个 fun
的模板参数 F
被解析为更期望的 double (*)(double)
函数指针类型,当调用fun时一个函数名称 .
但是,当我们将 fun(Test)
的调用更改为 fun(&Test)
以显式获取函数的地址(或显式函数指针)时,则 fun
将模板参数 F
解析为 double (*)(double)
!
这种行为似乎是所有Clang和GCC(以及Visual Studio 2013)的常见行为 .
那么问题是:在我的示例代码中给出的表单中,模板函数的函数类型模板参数推导规则是什么?
PS:如果我们添加 fun
的另一个实例来获取 F* f
,那么似乎重载规则决定选择此版本,并且根本没有报告任何歧义(尽管如我已经说过的那样,模糊不是最大的问题早些时候,但在最后一种情况下,我想知道为什么第三个版本是最好的匹配?)
template <typename F>
void fun(F* f)
{
}
2 回答
也许其他人可以比我更好地解释这一点,但这就是我理解它的方式(标准中没有引用,对不起) .
无法复制函数类型的变量,因此在
template <typename F> void fun(F f)
中,F
不能具有函数类型 .但是,函数类型的变量可以转换为指向函数类型的指针(这称为"decay",类似于数组到指针的转换),因此当函数类型与
template <typename F> void fun(F f)
匹配时,F
必须是指向函数的指针 .当处理对函数类型的引用时,函数到指针衰减不会发生(我在标准中找不到这个,但它应该与引用到数组规则一起描述),所以当匹配模板
<typename F> void fun(const F& f)
时,F
是函数类型(参数的类型是函数引用) .可能你已经弄明白了,因为你发布这个问题差不多已经3年了 . 但是如果你没有,我会给出答案 .
首先,请记住,数组和函数是奇怪的,因为数组可以隐式地衰减成指向其第一个元素的指针,并且函数可以隐式地衰减为函数指针 . And although syntactically valid, function parameters cannot actually be of array or function type but pointers ,意思是函数参数可以用数组或函数类型编写,但编译器将这些类型视为指针 . 例如,看下面的代码:
However ,当函数参数具有引用类型时,事情变得更加奇怪 . 具有对数组/函数的引用类型的函数参数被认为具有对数组/函数的引用类型, not 指针类型 . 例如:
关于你的第一个问题,由于你的第一个函数中的参数类型(
fun(const F& f)
)包含一个引用,当一个函数作为参数传递时,f
的类型将被推断为对函数的引用;更确切地说,f
的推导类型将是double (&) (double)
. 另一方面,由于第二个函数的参数类型不包含引用(fun(F f)
),因此当函数作为参数传递时,编译器隐式地将f
的类型推断为函数指针(f
的推导类型将为double (*)(double)
) . .好吧,既然你明确地将函数指针类型作为参数传递(通过获取
Test
的地址),推导出的f
类型必须有一个指针 . However, the reference and the constantness of the first function's parameter is not ignored . 运行fun(&Test)
时,第一个函数的推导类型f
将为double (* const &) (double)
并且第二个函数的推导类型f
将为double (*) (double)
.(我删除了该部分的先前答案,请参阅下文)
EDIT :在添加第三个函数(
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)
. 与之前相同,所有三个模板函数都具有相同的匹配性(实际上它们完全匹配) . 正因为如此,部分排序规则被用作最后的手段,事实证明第三个功能模板更专业,因此它被拾取 .关于 trivial conversions 的注释:虽然不完整,但从参数类型到参数类型的以下转换被认为是简单的转换(给定类型
T
):从
T
到const T
从
T
到T &
或从数组或函数类型到相应的指针类型(开头提到的衰变) .
EDIT #2 :似乎我可能没有使用正确的措辞,所以只需要清楚我的意思 function template 是一个创建函数的模板,并且 template function 是一个由模板创建的函数 .