【问题标题】:SFINAE with invalid function-type or array-type parameters?具有无效函数类型或数组类型参数的 SFINAE?
【发布时间】:2010-10-23 18:01:49
【问题描述】:

请考虑以下代码:

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] 描述的)之后完成的。

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

科莫说:

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

GCC 说(版本4.3.3):

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

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

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


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

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

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

【问题讨论】:

  • litb 要求标准说明是矛盾的 :)
  • 我在 usenet 上问过同样的问题,但也在这里发布了这个问题,以便可以在 SO 上存档,人们在 SO 上搜索时会找到它。使用网链接:groups.google.com/group/comp.lang.c++.moderated/browse_thread/…
  • 我认为 litb 是标准...?
  • 好吧,至少快速浏览一下标准可以证实 Comeau 的抱怨。根据 8.3.4,不允许使用函数数组。我只是略读了你的代码(有点匆忙的atm),所以我完全有可能错过了一些重要的东西;)
  • 我想我会等待 litb 回答这个问题...

标签: c++ arrays templates sfinae


【解决方案1】:

一个小笔记,虽然非常罕见,但我发现了一些我 相信 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 中列出的规则的哪个声明以及哪个参数类型?

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

【讨论】:

  • 嗯,关于多个声明的好点。实际上,我不知道那里选择了什么。以后得找找了。谢谢你的帮助,伙计:)我也认为现在最好说它首先衰减(使用“T作为元素/函数类型”),然后替换,然后再次衰减。
  • 越想这个,越觉得应该给它开个核心问题。我尝试了 Comeau 的一些示例,它似乎是第一个用于始终执行检查的函数类型。然而,这是不明智的,因为这意味着看到声明的顺序很重要——这不应该是这种情况。
  • 另一种可能性是声明具有相同参数类型列表的两个模板声明在功能上是等效的(在 14.6.6.1/6 中定义 - 到目前为止仅用于表达式,尚未用于参数类型)。但是如果未修改的参数类型列表相同,则说它们是等效的。那么,如果一个函数模板的声明不是等价的,而是功能上等价的,那么程序就是非良构的;无需诊断。
  • 我认为您提供了一个重要的见解:GCC 是正确的,因为如果声明使用 T[1],则允许​​ GCC 根据 14.5.5.1 [temp. over.link] - 但没有什么能阻止 Comeau 不改变它,因为毕竟两者都是等价的 - 但我认为 Comeau 仍然不应该失败 - 但让它成为演绎失败。由于这显然是一个矛盾(两个等效的声明一次失败,但另一次失败?),我同意你的观点,这是标准中的一个错误,并接受你的东西。非常感谢您的洞察力:)
【解决方案2】:

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

Visual Studio 正在解释

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

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

【讨论】:

    【解决方案3】:

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

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

    第一个重载的推导在步骤 1 中失败,因此重载解析返回第二个重载。我不认为该程序格式错误。

    【讨论】:

    • 演绎失败不应导致编译失败。 14.8.3/1 说“如果对于给定的函数模板,参数推导失败,则不会将此类函数添加到该模板的候选函数集中。”。没有任何地方说这样的演绎失败会导致程序格式错误。
    • 是的,我也是这么想的。看起来 Comeau 在 8.3.5 中尝试将 void[1]() 调整为 void()() 失败,然后注意到 void[1]() 是无效类型,不知道它在一个类型内扣减过程。如果它允许在那个阶段出错,我怀疑其他无效类型也必须出错,比如 template char (&f(T()[1])) [1];但在那里,它实际上导致了 SFINAE 错误(没有真正的诊断,但只是使用第二个重载)。
    猜你喜欢
    • 2010-10-04
    • 1970-01-01
    • 2017-01-14
    • 2014-01-12
    • 1970-01-01
    • 2022-01-21
    • 1970-01-01
    相关资源
    最近更新 更多