C++函数类型模板参数推导规则
C++ function type template parameter deduction rule
以下代码构建时
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 时,第一个fun
的模板参数F
被解析为纯函数类型double (double)
,而第二个fun
的模板参数F
被解析为更期望的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)
{
}
可能你已经想通了,因为你发布这个问题已经快 3 年了。但如果你还没有,我会给出我的答案。
有趣的是,当仅使用函数名称调用
fun
时,第一个fun
的模板参数F
被解析为纯函数类型double (double)
,而第二个fun
的模板参数F
被解析为更预期的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)
)时为什么不再有歧义的问题,我给出了一个非常草率的答案。我希望下面是一个明确的答案。
在函数模板的情况下,解析要选取哪个函数的规则是首先找出给定参数的模板专用集。这样做的原因是消除导致替换失败的函数模板作为候选。然后,根据从参数到参数的转换,从非模板函数的候选池和有效的模板专用化中消除较差的匹配。如果非模板和模板函数匹配良好,则选取非模板。如果多个模板函数同样匹配,则采用部分排序规则来消除不太专业的函数模板。如果一个作为最专业的功能模板大放异彩,那么它就解决了;另一方面,如果两者都不是更专业的,则编译器会发出歧义错误。不用说,如果没有找到有效的候选对象,则会再次发出错误。
现在让我们再次指出参数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
必须是指向函数的指针。
在处理对函数类型的引用时,函数到指针的衰减不会发生(我在标准中找不到这个,但它应该与对数组的引用规则一起描述),所以当匹配模板<typename F> void fun(const F& f)
时,F
是函数类型(参数的类型是对函数的引用)。
- 模板模板参数:以下示例中应用了什么规则
- 自动参数捕获的扣除规则是什么?
- 函数参数绑定通过参考与传递指针传递数组的规则
- C++模板函数的默认参数的 ODR 规则
- 模板参数推导和表达式规则
- 参数包扣减有哪些规则
- 具有多个包的参数包匹配规则
- C++函数类型模板参数推导规则
- 哪些规则控制参数默认赋值?
- 可以对提升精神规则进行参数化
- 强制函数参数匹配某些规则
- 默认模板参数是否进入单定义规则
- 当部分专用化参数不使用其任何模板参数时,哪些规则适用
- 参数替换的C++规则
- 智能指针和参数列表分配规则
- 可变参数模板上下文中"..."令牌的规则是什么?
- 对"non-type"模板参数强制实施规则
- 解析器规则依赖于参数
- 为什么在推导模板参数时应用 §5/5?规则不能不同吗?
- 隐藏规则-参数/返回类型必须相同吗?