【问题标题】:GCC does not disable a function based on SFINAE rulesGCC 不会禁用基于 SFINAE 规则的功能
【发布时间】:2019-01-25 16:49:34
【问题描述】:

在编译以下 Try it out on Coliru! 时,我期待 GCC 不考虑该函数

  template <typename DST, typename... Ts> 
  std::enable_if_t<sizeof...(Ts) == 0> CheckAndSetVal(DST&) {}

用于分析,因为 sizeof 条件不满足。

namespace SFINAE
{
  template <typename DST, typename... Ts> 
  std::enable_if_t<sizeof...(Ts) == 0> CheckAndSetVal(DST&) {}

  template <typename DST, typename T1, typename T2, typename... Ts>
  std::enable_if_t<!std::is_same_v<DST, T2> > CheckAndSetVal(DST& dst, T1&& cond, T2&& val, Ts&&... ts)
  {
    if (cond())
      dst = val();
    else
      CheckAndSetVal(dst, std::forward<Ts>(ts)...);
  }

  template <typename DST, typename T1, typename T2, typename... Ts>
  std::enable_if_t<std::is_same_v<DST, T2> > CheckAndSetVal(DST& dst, T1&& cond, T2&& val, Ts&&... ts)
  {
    if (cond())
      dst = val;
    else
      CheckAndSetVal(dst, std::forward<Ts>(ts)...);
  }

  template <typename DST, typename... Ts>
  void SetValue(DST& dst, Ts&&... ts)
  {
    CheckAndSetVal(dst, std::forward<Ts>(ts)...);
  }
}

int main()
{
  int i = 0;
  SFINAE::SetValue(i, []() { return true; }     , []() { return 222; }
                    , []() { return false; }    , 444
                  );
}

但我看到 GCC 抛出以下错误,恕我直言,这与它本身是矛盾的。它抱怨它找不到与 int&、lambda 和 int 作为参数的递归函数调用之一的匹配函数。但再次说候选人是应该被禁用的候选人,因为条件 sizeof...(Ts) == 0 是错误的。

candidate expects 1 argument, 3 provided

谁能帮我理解为什么会这样?

main.cpp: In instantiation of 'std::enable_if_t<(! is_same_v<DST, T2>)> SFINAE::CheckAndSetVal(DST&, T1&&, T2&&, Ts&& ...) [with DST = int; T1 = main()::<lambda()>; T2 = main()::<lambda()>; Ts = {main()::<lambda()>, int}; std::enable_if_t<(! is_same_v<DST, T2>)> = void]':
main.cpp:36:19:   required from 'void SFINAE::SetValue(DST&, Ts&& ...) [with DST = int; Ts = {main()::<lambda()>, main()::<lambda()>, main()::<lambda()>, int}]'
main.cpp:47:19:   required from here
main.cpp:21:21: error: no matching function for call to 'CheckAndSetVal(int&, main()::<lambda()>, int)'
       CheckAndSetVal(dst, std::forward<Ts>(ts)...);
       ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:13:40: note: candidate: 'template<class DST, class ... Ts> std::enable_if_t<(sizeof... (Ts) == 0)> SFINAE::CheckAndSetVal(DST&)'
   std::enable_if_t<sizeof...(Ts) == 0> CheckAndSetVal(DST&) {}
                                        ^~~~~~~~~~~~~~
main.cpp:13:40: note:   template argument deduction/substitution failed:
main.cpp:21:21: note:   candidate expects 1 argument, 3 provided
       CheckAndSetVal(dst, std::forward<Ts>(ts)...);
       ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:16:47: note: candidate: 'template<class DST, class T1, class T2, class ... Ts> std::enable_if_t<(! is_same_v<DST, T2>)> SFINAE::CheckAndSetVal(DST&, T1&&, T2&&, Ts&& ...)'
   std::enable_if_t<!std::is_same_v<DST, T2> > CheckAndSetVal(DST& dst, T1&& cond, T2&& val, Ts&&... ts)
                                               ^~~~~~~~~~~~~~
main.cpp:16:47: note:   template argument deduction/substitution failed:

【问题讨论】:

  • Fwiw,这个问题的标题是完全错误的。 gcc 正在正确禁用该功能
  • 我使用上面的标题来反映它在 VS2017 上编译得很好但在 GCC 上编译得很好。但是同样,标题中缺少关于 VS 的要点,这反过来又令人困惑。对不起……

标签: c++ c++17 variadic-templates sfinae enable-if


【解决方案1】:

首先,这个:

template <typename DST, typename... Ts> 
std::enable_if_t<sizeof...(Ts) == 0> CheckAndSetVal(DST&) {}

应该是:

template <typename DST> 
void CheckAndSetVal(DST&) {}

现在,一旦我们解决了这个问题,您的第二次重载看起来像:

template <typename DST, typename T1, typename T2, typename... Ts>
std::enable_if_t<!std::is_same_v<DST, T2> >
CheckAndSetVal(DST& dst, T1&& cond, T2&& val, Ts&&... ts)
{
  if (cond())
    dst = val();
  else
    CheckAndSetVal(dst, std::forward<Ts>(ts)...); // (*)
}

在某些情况下,标记的行想要调用 3rd 重载(例如在您的示例程序中)。但是第三个重载实际上还没有在范围内,ADL 也找不到它。标记行的唯一候选者是这个重载本身(它不是候选者,因为它是 SFINAE-d 出来的)和第一个重载(它不是候选者,因为它没有接受足够的参数)。

所以你必须:

  • 在第 2 个重载之前声明(但不定义)第 3 个重载,使其在第 2 个重载的范围内。
  • 在命名空间 SFINAE 中添加一个空类的第一个虚拟参数,以便 ADL 让您找到稍后声明的函数
  • 将所有这些成员operator()s 设为一个类,以便您可以看到稍后声明的函数,因为类主体是完整的类上下文。然后将CheckAndSetVal 设为该类类型的函数对象,而不是多个重载函数。

【讨论】:

  • 感谢您的快速回答。首先,关于没有“enable_if”的函数定义:我的原始版本实际上是这样的。我只是试图通过使用 enable_if 来强制 GCC 看不到它。其次,我很清楚它无法看到函数的第二个版本,但我假设在那个迭代中,只有一个函数应该是“活动的”(恕我直言,这是 SFINAE 的全部要点),函数的放置方式和位置无关紧要。一些 VS 似乎随之而来。那么你认为谁的行为正确?
  • @lightsunray 正确的行为是拒绝该程序。它的格式不正确。
猜你喜欢
  • 1970-01-01
  • 2021-02-18
  • 1970-01-01
  • 2020-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-03
  • 2017-11-04
相关资源
最近更新 更多