使用以下代码生成时
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被解析为更期望的函数指针类型。
然而,当我们将调用<代码>乐趣(测试) 更改为<代码>乐趣(
这种行为似乎是所有Clang和GCC(以及Visual Studio 2013)的常见行为。
那么问题是:在我的示例代码中给出的表单中,模板函数的函数类型模板参数推导规则是什么?
PS:如果我们添加另一个实例来获取F*F,那么重载规则似乎决定选择这个版本,并且没有报告任何歧义(尽管,正如我已经说过的,歧义不是之前最大的问题,但在最后一种情况下,我确实想知道为什么第三个版本是这里最匹配的?)
template <typename F>
void fun(F* f)
{
}
也许其他人能比我更好地解释这一点,但这就是我的理解(抱歉,没有引用标准)。
无法复制函数类型的变量,因此在模板中
但是,函数类型的变量可以转换为指向函数类型的指针(这称为“衰减”,就像数组到指针的转换),因此在将函数类型与模板匹配时
在处理对函数类型的引用时,函数到指针的衰减不会发生(我在标准中找不到这一点,但它应该与对数组规则的引用一起描述),因此在匹配模板<代码>
也许你已经弄明白了,因为你发布这个问题已经快三年了。但如果你没有回答,我会给出我的答案。
有趣的部分是第一个乐趣
的模板参数F
被解析为双(双)
的纯函数类型,而第二个乐趣
的模板参数F
被解析为更预期的双(*)(双)
函数指针类型,当乐趣
仅使用函数名称调用时。
首先,请记住数组和函数很奇怪,因为数组可以隐式衰减为指向其第一个元素的指针,而函数可以隐式衰减为函数指针。虽然在语法上有效,但函数参数实际上不能是数组或函数类型,而是指针,这意味着函数参数可以用数组或函数类型编写,但编译器将此类类型视为指针。例如,查看以下代码:
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)'
关于你的第一个问题,因为你的第一个函数中参数的类型(趣(const F
然而,当我们将调用<代码>乐趣(测试)
更改为<代码>乐趣(
现在,由于您显式地将函数指针类型作为参数传递(通过获取Test的地址),因此f的推导类型必须有一个指针。然而,第一个函数参数的引用和恒常性并没有被忽略。当<代码>乐趣(
PS:如果我们添加另一个实例来获取F*F,那么重载规则似乎决定选择这个版本,并且没有报告任何歧义(尽管,正如我已经说过的,歧义不是之前最大的问题,但在最后一种情况下,我确实想知道为什么第三个版本是这里最匹配的?)
(我删除了之前对该部分的回答,请参阅下文)
编辑:对于添加第三个函数(
fun(F*F)
)后,为什么不再有歧义的问题,我给出了一个非常草率的答案。我希望下面是一个明确的答案。
在函数模板的情况下,解析要选择哪个函数的规则是首先找出给定参数的模板专门化集。这样做的原因是为了消除导致替换失败的函数模板。然后,基于从参数到参数的转换,从非模板函数的候选池和有效的模板专门化中消除较差的匹配。如果非模板函数和模板函数同样匹配,则会拾取非模板函数。如果多个模板函数具有相同的良好匹配性,则采用偏序规则来消除不太专业化的函数模板。如果其中一个作为最专业的函数模板而闪耀,那么它就被解决了;另一方面,如果两者都不是更专业的,那么编译器会发出一个歧义错误。不用说,如果没有找到有效的候选者,将再次发出错误。
现在让我们再次指出参数
Test
的模板特殊化。如上所述,在模板类型推导之后,第一个函数模板的模板特殊化是uluf兴(双(
当您添加第三个函数模板(
ululify(F*f)
)时,模板类型推导会产生模板特化为ululify(双(*f)(双)
。和以前一样,所有三个模板函数都是同样好的匹配(事实上它们是完全匹配的)。同样由于这一点,部分排序规则被用作最后的手段,事实证明第三个函数模板更专业,因此被选中。
关于平凡转换的注意事项:虽然不完整,但以下从参数类型到参数类型的转换被认为是平凡的转换(给定类型
T
):
从T到常数T
编辑#2:看来我可能没有使用正确的措辞,所以要明确我所说的函数模板是指创建函数的模板,而模板函数是指由模板创建的函数。
如果我没有理解错的话,类模板定义了一个函数,所以当我调用时,编译器有可能进行隐式强制转换,但是在函数模板的情况下,此时没有函数定义,所以隐式强制转换不会发生。 但我不明白为什么编译器不能创建函数定义,然后应用隐式强制转换? 错误是: 在函数“int main()”中: 25:24:错误:调用“test2::add(void(&)(int))”没有匹配函数 25:24:注:候选人是:
我发现了和类型推导的以下行为,这对我来说是意想不到的: 中的两行都会导致错误: 没有函数模板“stdfunc_test”的实例与参数列表匹配 尝试在Visual Studio 2015中编译时。 为什么类型演绎不从函数类型中演绎模板类型,有没有变通方法?
在C++11之前,类模板和函数模板只能含有固定数量的模板参数。C++11增强了模板功能,允许模板定义中包含0到任意个模板参数,这就是可变参数模板。可变参数模板的加入使得C++11的功能变得更加强大,而由此也带来了许多神奇的用法。 可变参数模板 可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号...: template<ty
我已经编写了一个具有递归评估的可变参数模板函数。对于最后一个参数,我在没有可变参数包的情况下实现了专业化,并且一切正常。 现在我想把可变函数参数转换成模板参数。 这是我的尝试: gcc和clang报告了一个模糊的过载,并且无法使用空参数包在“专业化”和“可变参数”之间做出决定。 我尝试删除特化并检查可变参数模板中的包大小,但是如果没有特化,编译器就无法推断出参数“p”。
我创建了一个模板类,其中构造函数采用std::function对象。第一个模板参数指示该函数的返回值。第二个参数定义该函数的参数类型。 关于使用函数std::function 它尝试使用基本版本。但第二个参数当然丢失了。如果我指定它编译的模板参数。 那么,为什么选择基本模板作为executorSpecialization2呢?甚至可以在这里对void使用类型推断,而不需要传递模板参数吗? 谢啦
受这个答案的启发,我生成了这段代码,其输出取决于编译器: 如果使用 GCC 11 编译,调用