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);
}