代码之家  ›  专栏  ›  技术社区  ›  Johannes Schaub - litb

函数类型或数组类型参数无效的sfinae?

  •  22
  • Johannes Schaub - litb  · 技术社区  · 15 年前

    请考虑此代码:

    template<typename T>
    char (&f(T[1]))[1];
    
    template<typename T>
    char (&f(...))[2];
    
    int main() { char c[sizeof(f<void()>(0)) == 2]; }
    

    我希望它能做第二次超负荷的动作,因为它取代了 T 进入之内 T[1] 产量

     void [1]()
    

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

    但Comeau和GCC都未能编译上述内容。两者都有不同的诊断。

    科莫说:

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

    GCC说(版本 4.3.3 ):

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

    也就是说,GCC并没有不能替代,但它选择了 f 返回一个 sizeof 而不是没有像科莫那样提前替换它。

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


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

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

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

    3 回复  |  直到 15 年前
        1
  •  12
  •   Thomas L Holaday    15 年前

    一张小纸条,虽然很少见,但我发现有些场合 相信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]; }
    

    在上面,被替换的参数是指向 函数,而不是函数数组。

    所以,对我来说,问题是-如果有什么东西是Prohibs 功能参数(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中列出的规则,使用哪种声明,以及哪种参数类型?

    我的观点是,标准中没有提到上述内容。我还想说的是,任何有关这方面的措辞都必须将其保留为“未定义”或“实现定义”行为。

        2
  •  1
  •   Salman A    15 年前

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

    Visual Studio正在解释

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

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

        3
  •  1
  •   avakar    15 年前

    我将从阅读标准中了解模板参数推导的过程。

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

    第一次过载的推导在步骤1中失败,因此过载分辨率返回第二次过载。我不相信这个计划是错误的。