请考虑以下代码:
template<typename T>
char (&f(T[1]))[1];
template<typename T>
char (&f(...))[2];
int main() { char c[sizeof(f<void()>(0)) == 2]; }
我期望它做SFINAE并选择第二次重载,因为将T
替换为T[1]
会产生
void [1]()
当然,这是一种无效的类型。在将模板参数替换为函数参数并检查有效的结果类型(如14.8.2 [temp.deduct]描述)之后,完成参数类型(array-&gt;指针)的调整。
但是comeau和GCC都无法编译上述内容。两者都有不同的诊断。
Comeau说:
“ComeauTest.c”,第2行:错误:不允许使用函数数组
char (&f(T[1]))[1];
GCC说(版本4.3.3
):
错误:ISO C ++禁止零大小的数组
c
意思是,GCC不会替代,但它会选择f
的第一个重载,返回sizeof
为1,而不是像Comeau那样在前面替换它。
什么编译器是正确的,我的代码是否有效?请参阅或引用答案中适当的标准部分。谢谢!
更新:标准本身在14.8.2/2
列表中包含此类示例。我不知道为什么我先忽略它:
template <class T> int f(T[5]);
int I = f<int>(0);
int j = f<void>(0); // invalid array
虽然该示例仅提供信息,但它显示了所有这些神秘段落的意图,并且似乎表明上面的代码应该起作用并拒绝第一次重载。
答案 0 :(得分:12)
一个小小的音符,虽然非常罕见,但我发现了一些情况 相信Comeau编译器错了 - 尽管如此 场合是如此罕见,它总是值得加倍和三倍 检查你的假设!
我可能有一个g ++行为的原因。我不确定 在调整参数类型时确切指定:
请考虑以下事项:
template<typename T>
struct A
{
void bar (T[10]);
};
template<typename T>
void A<T>::bar (T*)
{
}
'bar'的定义是合法的,因为“T [10]”衰减为“T *”。我做 没有看到禁止编译器的标准中的任何内容 根据模板声明执行8.3.5的调整, 它还可以提高过载匹配的性能。
将此应用于您的示例,g ++可能会将其视为:
template<typename T>
char (&f( T* ))[1];
template<typename T>
char (&f(...))[2];
int main() { char c[sizeof(f<void()>(0)) == 2]; }
在上面,替换参数是一个合法的指针 函数,而不是函数数组。
所以,对我来说问题是 - 是否有某些东西被禁止 功能参数(8.3.5)的调整两次?
就个人而言,我认为允许进行调整是有意义的 两次,否则它使功能模板的匹配变得复杂 重载
总之,我认为它对g ++有效,可以选择第一个重载 基于它如何处理衰减的数组参数,而Comeau是错误的 不要对函数数组进行演绎失败。
当然这现在意味着(如果Comeau已经修复)那么每个编译器 会选择不同的超载,仍然是标准 兼容! :(
修改强>
为了说明我的观点,请考虑以下代码:
template <typename T> void foo ( T * );
template <typename T> void foo ( T * const );
template <typename T> void foo ( T [] );
template <typename T> void foo ( T [10] );
template <typename T> void foo ( T [100] );
void bar ()
{
foo < void() > ( 0 );
}
在这里,foo已经多次声明并重新声明。编译器应该使用哪种声明以及哪种参数类型应用14.8.2中列出的规则?
我的观点是该标准没有说明上述内容。我还要说,对此的任何措辞都必须将其作为“未定义”或“实现定义”行为。
答案 1 :(得分:1)
足够令人惊讶 - 这在VS2008中确实有效。我认为这不一定是正确行为的证据,但不是......
Visual Studio正在解释
char (&f(T[1]))[1];
作为一个函数,它接受一个T大小为1的数组,并返回一个大小为1的字符数组的引用。
答案 2 :(得分:1)
我将尝试描述模板参数推导的过程,正如我从阅读标准中所理解的那样。
第一次重载的推断在步骤1中失败,因此重载决策返回第二次重载。我不相信这个节目是不正确的。