is_invocable с произвольными типами аргументов функции - PullRequest
1 голос
/ 06 июля 2019

Есть ли способ использовать std::is_invocable с произвольными типами аргументов функций, например: std::is_invocable<Function, auto>.Идея состоит в том, чтобы проверить, может ли Function принимать 1 аргумент, независимо от типа аргумента.Для варианта использования рассмотрим две лямбда-выражения: auto lambda1 = [](auto x) {...}, auto lambda2 = [](auto x, auto y) {...} и шаблонную функцию более высокого порядка:

// specialize for 1 argument
template<typename Function, std::enable_if_t<(std::is_invocable<Function, auto>::value && !std::is_invocable<Function, auto, auto>::value)>, bool> = true>
void higherOrderFunc(Function&& func);

// specialize for 2 arguments
template<typename Function, std::enable_if_t<std::is_invocable<Function, auto, auto>::value, bool> = true>
void higherOrderFunc(Function&& func);

* В первом случае !std::is_invocable<Function, auto, auto>::value предотвращает неоднозначность для перегруженных функций (то естьпредпочтительной специализацией в этом случае будет 2 аргумента в случае двусмысленности).

Обратите внимание, что я знаю, что auto не может использоваться в этом случае таким образом.Я спрашиваю, есть ли способ реализовать это поведение (хотя бы частично).

1 Ответ

2 голосов
/ 06 июля 2019

Возможно, не идеальное решение ... но вы можете попробовать с passepartout

struct passepartout
 {
   template <typename T>
   operator T & ();

   template <typename T>
   operator T && ();
 };

Обратите внимание, что операторы преобразования только объявлены, но не определены; поэтому эта структура может использоваться в decltype() и std::declval()std::is_invocable), но не может быть реализована.

Теперь вы можете написать higherOrderFunc проходную ссылку на passepartout на std::is_invocable.

template <typename F,
   std::enable_if_t<
           std::is_invocable_v<F, passepartout &>
      && ! std::is_invocable_v<F, passepartout &, passepartout &>, bool>
         = true>
void higherOrderFunc (F)
 { std::cout << "-- one parameter callable" << std::endl; }

template <typename F,
   std::enable_if_t<
      std::is_invocable_v<F, passepartout &, passepartout &>, bool> = true>
void higherOrderFunc (F)
 { std::cout << "-- two parameter callable" << std::endl; }

Хитрость в том, что если вызываемый ожидает auto (или auto &, или auto &&), тип выводится как passepartout; когда вызываемое ожидание определенного типа (int, с или без ссылок, в следующих примерах), шаблон operator T & () (или operator T && (), в зависимости от случаев) совместим (в некотором смысле) с ожидаемым типом.

Ниже приведен полный пример компиляции

#include <type_traits>
#include <iostream>

struct passepartout
 {
   template <typename T>
   operator T & ();

   template <typename T>
   operator T && ();
 };

template <typename F,
   std::enable_if_t<
           std::is_invocable_v<F, passepartout &>
      && ! std::is_invocable_v<F, passepartout &, passepartout &>, bool>
         = true>
void higherOrderFunc (F)
 { std::cout << "-- one parameter callable" << std::endl; }

template <typename F,
   std::enable_if_t<
      std::is_invocable_v<F, passepartout &, passepartout &>, bool> = true>
void higherOrderFunc (F)
 { std::cout << "-- two parameter callable" << std::endl; }

int main ()
 {
   auto l1a = [](auto &&){};
   auto l1b = [](int &){};
   auto l2a = [](auto &, int &&){};
   auto l2b = [](auto, int const &){};
   auto l2c = [](auto &&, auto const &){};
   auto l2d = [](int &&, auto const &, auto && ...){};

   higherOrderFunc(l1a);
   higherOrderFunc(l1b);
   higherOrderFunc(l2a);
   higherOrderFunc(l2b);
   higherOrderFunc(l2c);
   higherOrderFunc(l2c);
   higherOrderFunc(l2d);
 }
...