21

请考虑以下代码:

template<typename T>
char (&f(T[1]))[1];

template<typename T>
char (&f(...))[2];

int main() { char c[sizeof(f<void()>(0)) == 2]; }

我预计它会做 SFINAE 并选择第二个重载,因为替换TT[1]产量

 void [1]()

当然,这是无效的类型。在将模板参数替换为函数参数并检查有效的结果类型(如 14.8.2 [temp.deduct] 描述)之后,完成参数类型(数组->指针)的调整。

但是 comeau 和 GCC 都无法编译上述内容。两者都有不同的诊断。

科莫 说:

“ComeauTest.c”,第 2 行:错误:不允许使用函数数组char (&f(T[1]))[1];

GCC 说(版本4.3.3):

错误:ISO C++ 禁止零大小数组c

意思是,GCC 没有替换失败,但是它选择了 的第一个重载f,返回sizeof1 的 a,而不是像 Comeau 那样没有替换它。

什么编译器是正确的,我的代码是否有效?请在您的答案中参考或引用适当的标准部分。谢谢!


更新:标准本身在列表中包含这样一个示例14.8.2/2。我不知道,为什么我首先忽略了它:

template <class T> int f(T[5]);
int I = f<int>(0);
int j = f<void>(0); // invalid array

虽然这个例子只是提供信息,但它显示了所有这些神秘段落的意图,并且似乎表明上面的代码应该可以工作并拒绝第一个重载。

4

3 回答 3

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 中列出的规则,哪个声明,以及哪个参数类型?

我的观点是,该标准对上述内容没有任何说明。我还要说,任何关于此的措辞都必须将其保留为“未定义”或“实现定义”的行为。

于 2009-05-05T13:32:25.413 回答
1

令人惊讶的是——这在 VS2008 中确实有效。我认为这不一定是正确行为的证据,尽管...

Visual Studio 正在解释

char (&f(T[1]))[1];

作为一个函数,它接受一个大小为 1 的 T 数组,并返回一个对大小为 1 的字符数组的引用。

于 2009-05-04T21:14:27.980 回答
1

我将尝试描述我从阅读标准中理解的模板参数推导的过程。

  1. 如 14.8.2/2 中所述检查显式模板参数。
  2. 根据 8.3.5 调整生成的函数签名(即执行数组到指针衰减)。
  3. 根据 14.8.2.1 推导出隐式模板参数(这是在步骤 2 中的部分替换签名上执行的)。

第一个重载的推导在步骤 1 中失败,因此重载解析返回第二个重载。我不相信这个程序是错误的。

于 2009-05-04T21:34:52.347 回答