【问题标题】:disable template member function if return type is an array如果返回类型是数组,则禁用模板成员函数
【发布时间】:2024-04-20 07:45:02
【问题描述】:

https://www.godbolt.org/z/_4aqsF:

template <typename T> struct Container
{
    template <typename TPred> T find_if(TPred pred);  // the culprit
};

template <typename T> Container<T> MakeContainer(T const &)
{
    return Container<T>();    
}

int main()
{
    auto x = MakeContainer("Hello!");
}

gcc、clang 和 msvc 显然同意这无法编译,因为 find_if 会返回一个数组。

(我会假设成员模板没有被实例化,因为它没有被使用 - 显然,这种简单化的观点是错误的。)

为什么 SFINAE 不适用于这里?

有没有办法为 T 不是可返回类型的类型排除成员模板?

【问题讨论】:

    标签: c++ templates sfinae template-argument-deduction


    【解决方案1】:

    SFINAE 不起作用,因为在 MakeContainer 重载的 SFINAE 期间不会检查在您的 MakeContainer 返回点中生成的类型的成员。

    SFINAE 仅在直接上下文中发生。类型体和函数体不在范围内,不会导致替换失败。

    template <typename T=char[7]> Container<char[7]> MakeContainer(char const (&)[7])
    

    这个签名很好。

    选择后,Container&lt;char[7]&gt; 将被实例化并解析其方法。

    template <typename TPred> char[7] find_if(TPred pred);  // the culprit
    

    没有TPred 可能导致此find_if 成为有效方法,因此您的程序格式不正确,不需要诊断。

    正确的解决方法是:

    template <typename T> struct Container
    {
      template <typename TPred> T find_if(TPred pred);  // the culprit
    };
    template <class T, std::size_t N> struct Container<T[N]>:
      Container<std::array<T,N>>
    {
      using Container<std::array<T,N>>::Container;
    };
    

    当然,Container&lt;std::array&lt;T,N&gt;&gt; 本身需要一个非常特殊的find_if 并且可能需要构造函数。但至少它不会立即中断。

    【讨论】:

      【解决方案2】:

      SFINAE 从重载集合中删除在模板参数推导期间非法的重载。

      这里,重载集只包含一个候选者:MakeContainer&lt;const char (&amp;)[7]&gt;。模板参数推导到此结束。没有歧义。一切都很好。

      然后,类型Container&lt;const char (&amp;)[7]&gt; 被实例化。它会生成一个模板化函数 (Container&lt;const char (&amp;)[7]&gt;::find_if),其签名是非法的(所有这些,因为 T 是在 find_if 的上下文中推断出来的)。 SFINAE 没有发挥作用。

      现在,您可以将一些 SFINAE 添加到容器的 find_if 函数中,方法是使其返回类型取决于其模板参数。为此,请参阅max66's answer

      【讨论】:

      • “这里,重载集只包含一个候选......没有歧义” - 谢谢! ,这方面特别帮助我理解。
      【解决方案3】:

      要在fidn_if 上使用 SFINAE,您需要使用函数本身的相关参数,这是 SFINAE 在不可返回类型上的版本:

      template <typename TPred, class U = T, typename std::enable_if<
             std::is_same<T, U>::value
          && !std::is_abstract<U>::value
          && !std::is_function<U>::value
          && !std::is_array<U>::value
          , bool>::type = true>
      U find_if(TPred pred);
      

      【讨论】:

      • 接受这个答案,因为它似乎最正式/便携/代码明确地回答了我的问题。尽管如此,还是从这里的每个人那里学到了有趣和美好的东西!
      【解决方案4】:

      试试

      template <typename TPred, typename U = T>
      U find_if (TPred pred);  // the culprit
      

      SFINAE,over 方法,不适用于类的模板参数。适用于方法本身的模板。因此,您必须使 SFINAE 替换依赖于方法本身的模板参数。

      所以不是T,而是U

      如果您担心有人会“劫持”您的函数,说明模板类型如下

      auto x = MakeContainer("Hello!");
      
      x.find_if<int, int>(1);
      

      你可以强加UT是同一类型

      template <typename TPred, typename U = T>
      typename std::enable_if<std::is_same<U, T>::value, U>::type
          find_if (TPred pred)  // the culprit
      

      【讨论】: