Предикат времени компиляции С++ для проверки, может ли вызываемый объект типа F вызываться с аргументом типа T

Я хотел бы создать функцию типа компиляции, которая, учитывая любой вызываемый объект f (функция, лямбда-выражение, функциональный объект,...) и тип T, оценивается как true, если f может быть вызван с аргументом типа T, а false, если он не может.

Пример:

void f1(int) { ... }
void f2(const std::string&) { ... }

assert( is_callable_with<int>(f1));
assert(!is_callable_with<int>(f2));

Я думаю, что разумное использование правила SFINAE могло бы достичь этого. Возможно, так или иначе:

template<typename T, typename F>
constexpr bool is_callable_with(F&&, typename std::result_of<F(T)>::type* = nullptr) {
  return true;
}

template<typename T, typename F>
constexpr bool is_callable_with(F&&) {
  return false;
}

Но это не сработает, потому что если f можно вызывать с помощью T, обе перегрузки участвуют в разрешении перегрузки, и есть двусмысленность. Я бы хотел переписать его, так что в положительном случае первая перегрузка будет выбрана с помощью разрешения перегрузки над вторым. Не уверен, что я даже на правильном пути здесь.

Ответы

Ответ 1

Вариант Paul answer, но после стандартного тестового шаблона SFINAE. Опять же общий признак с произвольными параметрами A...:

struct can_call_test
{
    template<typename F, typename... A>
    static decltype(std::declval<F>()(std::declval<A>()...), std::true_type())
    f(int);

    template<typename F, typename... A>
    static std::false_type
    f(...);
};

template<typename F, typename... A>
using can_call = decltype(can_call_test::f<F, A...>(0));

Затем функция a constexpr по вашему запросу:

template<typename T, typename F>
constexpr bool is_callable_with(F&&) { return can_call<F, T>{}; }

Отметьте живой пример.

Это будет работать с функциями, лямбда-выражениями или объектами функций с произвольным числом аргументов, но для функций-членов (указателей на) вам придется использовать std::result_of<F(A...)>.


UPDATE

Ниже can_call имеет синтаксис синтаксиса "function signature" std::result_of:

template<typename F, typename... A>
struct can_call : decltype(can_call_test::f<F, A...>(0)) { };

template<typename F, typename... A>
struct can_call <F(A...)> : can_call <F, A...> { };

который будет использоваться таким образом

template<typename... A, typename F>
constexpr can_call<F, A...>
is_callable_with(F&&) { return can_call<F(A...)>{}; }

где я также сделал is_callable_with variadic (я не могу понять, почему он должен быть ограничен одним аргументом) и возвращает тот же тип, что и can_call вместо bool (спасибо Yakk).

Опять же, живой пример здесь.

Ответ 2

Сначала я бы сделал черту типа:

template<class X = void>
struct holder
{
    typedef void type;
};

template<class F, class T, class X = void>
struct is_callable_with_trait
: std::false_type
{};

template<class F, class T>
struct is_callable_with_trait<F, T, typename holder<
    decltype(std::declval<F>()(std::declval<T>()))
>::type>
: std::true_type
{};

И тогда, если вы хотите, вы можете превратить его в функцию:

template<typename T, typename F>
constexpr bool is_callable_with(F&&) 
{
    return is_callable_with_trait<F&&, T>::value;
}

Ответ 3

template<class F, class T, class = void>
struct is_callable_with_impl : std::false_type {};

template<class F, class T>
struct is_callable_with_impl<F,T,
     typename std::conditional< 
              true,
              void,
              decltype( std::declval<F>() (std::declval<T>()) ) >::type
      > : std::true_type {};

template<class T, class F>
constexpr bool is_callable_with(F &&) 
{ 
     return is_callable_with_impl< F, T >::value; 
}

Это в основном то же самое решение, что и сообщение Paul, я предпочитаю использовать conditional<true, void, decltype( ... ) > вместо класса holder, чтобы избежать загрязнения пространства имен.