考虑以下代码:
#include <iostream>
void f(int) { }
void f(int, short) { }
template<typename... Ts> void g(void (*)(Ts...))
{
std::cout << sizeof...(Ts) << '\n';
}
template<typename T, typename... Ts> void h(void (*)(T, Ts...))
{
std::cout << sizeof...(Ts) << '\n';
}
int main()
{
g(f); // #1
g<int>(f); // #2
h(f); // #3
h<int>(f); // #4
}
目的是分别尝试正文中的每一行main()
。我的期望是所有四个调用都是模棱两可的,并且会导致编译器错误。
我在以下位置测试了代码:
- Clang 3.6.0 和 GCC 4.9.2,都使用
-Wall -Wextra -pedantic -std=c++14
(-std=c++1y
for GCC) - 在所有这些情况下的行为相同,除了错误消息的措辞略有不同; - Visual C++ 2013 Update 4 和 Visual C++ 2015 CTP6 - 同样的行为,所以我将它们称为“MSVC”。
Clang 和 GCC:
#1
: 编译器错误,带有令人困惑的消息,基本上no overload of 'f' matching 'void (*)()'
。什么?无参数声明从何而来?#3
:编译器错误,带有另一个令人困惑的消息:couldn't infer template argument 'T'
。在所有可能失败的事情中,推论 forT
将是我所期望的最后一个......#2
and#4
:编译时没有错误和警告,并选择第一个重载。
对于所有四种情况,如果我们消除其中一个重载(任何一个),代码编译良好并选择剩余的函数。这看起来像是 Clang 和 GCC 中的不一致:毕竟,如果两个重载分别推导成功,那么在 case#2
和中如何选择一个而不是另一个#4
?他们俩不是绝配吗?
现在,MSVC:
#1
,#3
和#4
: 编译器错误,带有很好的消息:cannot deduce template argument as function argument is ambiguous
. 现在这就是我要说的!可是等等...#2
:编译没有错误和警告,并选择第一个重载。分别尝试两个重载,只有第一个匹配。第二个产生错误:cannot convert argument 1 from 'void (*)(int,short)' to 'void (*)(int)'
. 不再那么好了。
为了澄清我在寻找什么 case #2
,这就是标准(N4296,C++14 final 之后的初稿)在 [14.8.1p9] 中所说的:
模板实参推导可以扩展与模板形参包对应的模板实参序列,即使该序列包含显式指定的模板实参。
看起来这部分在 MSVC 中不太适用,使其选择第一个重载为#2
.
到目前为止,看起来 MSVC 虽然不太正确,但至少是相对一致的。Clang 和 GCC 是怎么回事?根据每种情况的标准,正确的行为是什么?