GCC не отключает функцию, основанную на правилах SFINAE - PullRequest
0 голосов
/ 25 января 2019

При компиляции следующего Попробуйте на 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:

1 Ответ

0 голосов
/ 25 января 2019

Во-первых, это:

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 (например, в вашей программе примера). Но 3-я перегрузка фактически еще не находится в области действия, и она не может быть найдена ADL. Единственные кандидаты на выделенную строку - это сама перегрузка (которая не является кандидатом, потому что она SFINAE-d out) и первая перегрузка (которая не является кандидатом, потому что она не принимает достаточного количества аргументов).

Так что вы должны либо:

  • Объявите (но не определяйте) 3-ю перегрузку перед 2-й, чтобы она находилась в области действия 2-й.
  • Добавить фиктивный 1-й параметр, который является некоторым пустым классом в пространстве имен SFINAE, чтобы ADL позволял вам находить объявленные позже функции
  • Сделайте все эти члены operator()s класса, чтобы вы могли видеть позже объявленные функции, потому что тела классов являются контекстами полного класса. И затем сделайте CheckAndSetVal функциональным объектом этого типа класса, а не несколькими перегруженными функциями.
...