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

16

考虑到这段代码,模板参数推导如何决定最后一个函数调用的操作?

#include <iostream>

template<typename Ret, typename... Args>
Ret foo(Args&&...) {
    std::cout << "not void\n";
    return {};
}

template<typename... Args>
void foo(Args&&...) {
    std::cout << "void\n";
}

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)存在歧义。


我不知道答案,但如果我必须猜的话,第4个选项适用于第一和第二个模板签名,并且编译器根据优先级选择第一个,可能是因为模板定义先出现或者因为它更加专业化。再次强调,这只是我的个人意见。 - Bee
@bee 我在这个问题中没有看到任何特化。重载是肯定的,但没有特化。 - Yakk - Adam Nevraumont
@aaronman,谢谢您进行测试。在我看到 LWS 仍然失效之前,我打算进行测试。只有(4)不能编译吗?还是还有其他错误? - chris
是的,只有4个,因为存在歧义,GCC可能存在错误。 - aaronman
@aaronman,我还没有尝试使用MSVC,可能是因为它支持C++11,但CTP支持它。它给出了与Clang相同的结果。因此,我现在应该去睡觉了,因为明天我有AP考试。我猜我期待着是否会有任何进展。 - chris
显示剩余4条评论
1个回答

4

我认为Clang是正确的(仍在3.3 SVN中),根据部分排序规则,(4)中的调用是含糊不清的 - 两个函数模板均可行且都不比另一个 更特殊。请注意,通过将可变参数包转换为单个参数,GCC也可以被强制产生相同的输出:

#include <iostream>

template<typename Ret, typename T>
Ret foo(T&&) {
    std::cout << "not void\n";
    return {};
}

template<typename T>
void foo(T&&) {
    std::cout << "void\n";
}

int main() {
    foo<int>(3);
}

输出:

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

现场示例。

这是一个现场示例的链接。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接