当前位置: 首页 > 知识库问答 >
问题:

C函数类型模板参数推导规则

佴阳曦
2023-03-14

使用以下代码生成时

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)
{
}

共有2个答案

商宝
2023-03-14

也许其他人能比我更好地解释这一点,但这就是我的理解(抱歉,没有引用标准)。

无法复制函数类型的变量,因此在模板中

但是,函数类型的变量可以转换为指向函数类型的指针(这称为“衰减”,就像数组到指针的转换),因此在将函数类型与模板匹配时

在处理对函数类型的引用时,函数到指针的衰减不会发生(我在标准中找不到这一点,但它应该与对数组规则的引用一起描述),因此在匹配模板<代码>

盖向荣
2023-03-14

也许你已经弄明白了,因为你发布这个问题已经快三年了。但如果你没有回答,我会给出我的答案。

有趣的部分是第一个乐趣的模板参数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):

  1. 从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 编译,调用