NoneIs _invocable с произвольными типами аргументов функции

Есть ли способ использовать 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 ответ

Решение

Может быть, не идеальное решение... но вы можете попробовать с 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);
 }
Другие вопросы по тегам