在这种情况下模板参数推导是如何工作的

How does template argument deduction work in this case?

本文关键字:何工作 工作 这种情况下 参数      更新时间:2023-10-16

给定这段代码,模板参数推导如何决定对最后一个函数调用做什么?

#include <iostream>
template<typename Ret, typename... Args>
Ret foo(Args&&...) {
    std::cout << "not voidn";
    return {};
}
template<typename... Args>
void foo(Args&&...) {
    std::cout << "voidn";
}
int main() {
    foo(3, 'a', 5.4);            //(1): prints "void"
    foo<int, char>(3, 'a', 5.4); //(2): prints "void"
    foo<int>('a', 5.4);          //(3): prints "not void"
    foo<int>(3, 'a', 5.4);       //(4): prints "not void"
}

(1)似乎很简单。它不能推断返回类型,所以使用void版本。

(2)显式地声明了一些参数的类型。第一个模板实参与第一个实参匹配,第二个模板实参与第二个实参匹配,然后推导出第三个模板实参。如果使用int作为返回类型,则char将不匹配第一个参数。

(3)与(2)做同样的事情,但前两种类型不匹配。因此,必须将其推断为返回类型和用于推断两个Args参数的两个参数。

(4)似乎模棱两可。它显式地指定了一个模板实参,就像(2)和(3)一样。模板实参匹配实参,就像(2)一样。但是,它不使用模板实参作为第一个实参并推导出其他两个实参,而是使用显式模板实参作为返回类型并推导出所有三个Args实参。


为什么(4)似乎一半遵循(2),但又使用另一个版本?我最好的猜测是,要填写的单个模板参数比只填写参数包更匹配。标准在哪里定义了这种行为?

这是使用GCC 4.8.0编译的。为方便起见,下面是在Coliru上运行的一个测试。

然而,在Clang 3.1中,由于歧义(见注释),(4)不能编译。这就有可能导致这两个编译器中的一个存在bug。使这种可能性更有可能的是,Visual Studio 2012 11月CTP编译器给出了与Clang相同的结果,其中(4)是模糊的。

我相信Clang是正确的(仍然在3.3 SVN中),(4)中的调用根据偏排序规则是模糊的-两个函数模板都是可行的,都不比更专门化。

注意,GCC可以通过将可变包转换为单个参数来强制提供相同的输出:

#include <iostream>
template<typename Ret, typename T>
Ret foo(T&&) {
    std::cout << "not voidn";
    return {};
}
template<typename T>
void foo(T&&) {
    std::cout << "voidn";
}
int main() {
    foo<int>(3);
}
输出:

main.cpp: In function 'int main()':  
main.cpp:15:15: error: call of overloaded 'foo(int)' is ambiguous

生活例子。