Dej*_*avu 10 c++ templates function-pointers
使用时构建以下代码
clang -Wall main.cpp -o main.o
Run Code Online (Sandbox Code Playgroud)
生成以下诊断(在代码之后):
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;
}
Run Code Online (Sandbox Code Playgroud)
诊断:
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.
Run Code Online (Sandbox Code Playgroud)
有趣的部分不是模糊性错误本身(这不是主要关注点).有趣的是,模板参数F
第一的fun
决心是纯功能型double (double)
,而模板参数F
第二的fun
解决是更期望double (*)(double)
的函数指针类型,当乐趣与功能名称单独调用.
然而,当我们改变的调用fun(Test)
是fun(&Test)
明确把函数的地址(或显式函数指针),则既fun
解决模板参数F
是double (*)(double)
!
这种行为似乎是所有Clang和GCC(以及Visual Studio 2013)的常见行为.
那么问题是:在我的示例代码中给出的表单中,模板函数的函数类型模板参数推导规则是什么?
PS:如果我们添加另一个fun
要采取的实例F* f
,那么似乎重载规则只是决定选择这个版本,并且根本没有报告歧义(尽管如我已经说过的那样,模糊不是早期最关注的问题,但在最后一种情况下,我想知道为什么第三个版本是最好的匹配?)
template <typename F>
void fun(F* f)
{
}
Run Code Online (Sandbox Code Playgroud)
小智 5
可能你已经弄明白了,因为你发布这个问题差不多已经3年了.但是如果你没有,我会给出答案.
有趣的是,模板参数
F
第一的fun
决心是纯功能型double (double)
,而模板参数F
第二的fun
解决是更期望double (*)(double)
的函数指针类型,当fun
用一个函数名只调用.
首先,请记住,数组和函数是奇怪的,因为数组可以隐式地衰减成指向其第一个元素的指针,并且函数可以隐式地衰减为函数指针.虽然语法上有效,但函数参数实际上不能是数组或函数类型,而是指针,意味着函数参数可以用数组或函数类型编写,但编译器会将这些类型视为指针.例如,看下面的代码:
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)'
Run Code Online (Sandbox Code Playgroud)
但是,当函数参数具有引用类型时,事情变得更加奇怪.具有对数组/函数的引用类型的函数参数被认为具有对数组/函数的引用类型,而不是指针类型.例如:
void bar(int (& x)[2]); //type of x is now 'int (&) [2]'
void bar(int (& x)(int)); //type of x is now 'int (&)(int)'
Run Code Online (Sandbox Code Playgroud)
关于你的第一个问题,因为yours(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)
添加第三个函数()时,我对如何没有歧义的问题给出了一个非常草率的答案.我希望下面是一个明确的答案.
在函数模板的情况下解析哪个函数的规则首先要找出给定参数的模板特化集.其原因是消除导致替换失败的功能模板作为候选.然后,基于从参数到参数的转换,从非模板函数的候选池和有效的模板特化中消除了更差的匹配.如果非模板和模板函数同样匹配,则拾取非模板.如果多个模板函数同样匹配良好,则采用部分排序规则来消除不太专业的函数模板.如果一个作为最专业的功能模板闪耀,那么它就解决了; 另一方面,如果两者都不是更专业,那么编译器会发出歧义错误.不用说,如果找不到有效候选者,则再次发出错误.
现在让我们再次指出参数的模板特化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 &
编辑#2:似乎我可能没有使用正确的措辞,所以只是要清楚我的意思是功能模板是一个创建函数的模板,而模板函数是由模板创建的函数.
也许其他人可以比我更好地解释这一点,但这就是我的理解方式(抱歉,没有引用标准)。
无法复制函数类型的变量,因此在 中template <typename F> void fun(F f)
,F
不能具有函数类型。
然而,函数类型的变量可以转换为指向函数类型的指针(这称为“衰减”,就像数组到指针的转换),因此当将函数类型与 匹配时,必须是template <typename F> void fun(F f)
指向F
函数的指针。
当处理对函数类型的引用时,函数到指针的衰减不会发生(我在标准中找不到这一点,但它应该与引用数组规则一起描述),因此当匹配 template 时<typename F> void fun(const F& f)
,F
是函数类型(参数的类型是函数引用)。