Подтвердить что ты не робот

С++ metafunction, чтобы определить, является ли тип вызываемым

Возможно ли написать мета-функцию С++ (0x), которая определяет, является ли тип вызываемым?

Под вызываемым типом я подразумеваю тип функции, тип указателя функции, тип ссылочного типа (они обнаруживаются с помощью boost::function_types::is_callable_builtin), типы лямбда и любой класс с перегруженным operator() (и, возможно, любым классом с неявным преобразованием оператора к одному из них, но это не совсем необходимо).

РЕДАКТИРОВАТЬ. Metafunction должен обнаруживать наличие operator() с любой подписью, включая шаблонный operator(). Я считаю, что это трудная часть.

EDIT. Вот пример использования:

template <typename Predicate1, typename Predicate2>
struct and_predicate
{
    template <typename ArgT>
    bool operator()(const ArgT& arg)
    {
        return predicate1(arg) && predicate2(arg);
    }

    Predicate1 predicate1;
    Predicate2 predicate2;
};

template <typename Predicate1, typename Predicate2>
enable_if<ice_and<is_callable<Predicate1>::value,
                  is_callable<Predicate2>::value>::value,
          and_predicate<Predicate1, Predicate2>>::type
operator&&(Predicate1 predicate1, Predicate2 predicate2)
{
    return and_predicate<Predicate1, Predicate2>{predicate1, predicate2};
}

is_callable - это то, что я хотел бы реализовать.

4b9b3361

Ответ 1

Наличие не templated T:: operator() для заданного типа T может быть обнаружено:

template<typename C> // detect regular operator()
static char test(decltype(&C::operator()));

template<typename C> // worst match
static char (&test(...))[2];

static const bool value = (sizeof( test<T>(0)  )

Наличие шаблонизированного оператора может быть обнаружено:

template<typename F, typename A> // detect 1-arg operator()
static char test(int, decltype( (*(F*)0)( (*(A*)0) ) ) = 0);

template<typename F, typename A, typename B> // detect 2-arg operator()
static char test(int, decltype( (*(F*)0)( (*(A*)0), (*(B*)0) ) ) = 0);

// ... detect N-arg operator()

template<typename F, typename ...Args> // worst match
static char (&test(...))[2];

static const bool value = (sizeof( test<T, int>(0)  ) == 1) || 
                          (sizeof( test<T, int, int>(0)  ) == 1); // etc...

Однако эти два не играют хорошо вместе, так как decltype (& C:: operator()) выдает ошибку, если C имеет шаблонный оператор вызова функции. Решение состоит в том, чтобы сначала выполнить последовательность проверок против шаблонного оператора и проверить регулярный оператор(), если и только если шаблонный не может быть найден. Это делается, специализируясь на не templated check для no-op, если был найден шаблонный.

template<bool, typename T>
struct has_regular_call_operator
{
  template<typename C> // detect regular operator()
  static char test(decltype(&C::operator()));

  template<typename C> // worst match
  static char (&test(...))[2];

  static const bool value = (sizeof( test<T>(0)  ) == 1);
};

template<typename T>
struct has_regular_call_operator<true,T>
{
  static const bool value = true;
};

template<typename T>
struct has_call_operator
{
  template<typename F, typename A> // detect 1-arg operator()
  static char test(int, decltype( (*(F*)0)( (*(A*)0) ) ) = 0);

  template<typename F, typename A, typename B> // detect 2-arg operator()
  static char test(int, decltype( (*(F*)0)( (*(A*)0), (*(B*)0) ) ) = 0);

  template<typename F, typename A, typename B, typename C> // detect 3-arg operator()
  static char test(int, decltype( (*(F*)0)( (*(A*)0), (*(B*)0), (*(C*)0) ) ) = 0);

  template<typename F, typename ...Args> // worst match
  static char (&test(...))[2];

  static const bool OneArg = (sizeof( test<T, int>(0)  ) == 1);
  static const bool TwoArg = (sizeof( test<T, int, int>(0)  ) == 1);
  static const bool ThreeArg = (sizeof( test<T, int, int, int>(0)  ) == 1);

  static const bool HasTemplatedOperator = OneArg || TwoArg || ThreeArg;
  static const bool value = has_regular_call_operator<HasTemplatedOperator, T>::value;
};

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

Ответ 2

С появлением нашего коллективного опыта в С++ 11 (и за его пределами), вероятно, пора пересмотреть этот вопрос.

Мне кажется, что эта небольшая черта характерна для меня:

#include <iostream>
#include <utility>

template<class F, class...Args>
struct is_callable
{
    template<class U> static auto test(U* p) -> decltype((*p)(std::declval<Args>()...), void(), std::true_type());
    template<class U> static auto test(...) -> decltype(std::false_type());

    static constexpr bool value = decltype(test<F>(0))::value;
};

Что мы можем проверить таким образом:

template<class F, class...Args, typename std::enable_if<is_callable<F, Args&&...>::value>::type* = nullptr>
void test_call(F, Args&&...args)
{
    std::cout << "callable" << std::endl;
}

template<class F, class...Args, typename std::enable_if<not is_callable<F, Args&&...>::value>::type* = nullptr>
void test_call(F, Args&&...args)
{
    std::cout << "not callable" << std::endl;
}

extern void f3(int, const std::string&)
{

}

int main()
{
    auto f1 = [](int, std::string) {};
    test_call(f1, 0, "hello");
    test_call(f1, "bad", "hello");

    std::function<void(int, const std::string&)> f2;
    test_call(f2, 0, "hello");
    test_call(f2, "bad", "hello");

    test_call(f3, 0, "hello");
    test_call(f3, "bad", "hello");

}

ожидаемый вывод:

callable
not callable
callable
not callable
callable
not callable

Ответ 3

Это действительно интересный вопрос. Я был озадачен этим.

Я думаю, мне удалось внести изменения в код Crazy Eddie, который позволит любое количество параметров, однако он использует вариационные шаблоны, и для этого требуется указать параметры, которые вы ожидаете от вызываемого объекта, вызываемого с помощью, Короче говоря, я получил эту работу и работал, как ожидалось, на gcc 4.6.0:

РЕДАКТИРОВАТЬ: Можно также использовать утилиту std:: result_of, однако она не работает, потому что для typename требуется disambiguate std::result_of<..>::type, которая разбивает Sfinae.

#include <iostream>
#include <type_traits>

template < typename PotentiallyCallable, typename... Args>
struct is_callable
{
  typedef char (&no)  [1];
  typedef char (&yes) [2];

  template < typename T > struct dummy;

  template < typename CheckType>
  static yes check(dummy<decltype(std::declval<CheckType>()(std::declval<Args>()...))> *);
  template < typename CheckType>
  static no check(...);

  enum { value = sizeof(check<PotentiallyCallable>(0)) == sizeof(yes) };
};

int f1(int,double) { return 0; };
typedef int(*f1_type)(int,double) ; //this is just to have a type to feed the template.

struct Foo { };

struct Bar {
  template <typename T>
  void operator()(T) { };
};

int main() {
  if( is_callable<f1_type,int,double>::value )
    std::cout << "f1 is callable!" << std::endl;
  if( is_callable<Foo>::value )
    std::cout << "Foo is callable!" << std::endl;
  if( is_callable<Bar,int>::value )
    std::cout << "Bar is callable with int!" << std::endl;
  if( is_callable<Bar,double>::value )
    std::cout << "Bar is callable with double!" << std::endl;
};

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

EDIT: Для вашего варианта использования это частичное решение, но оно может помочь:

template <typename Predicate1, typename Predicate2>
struct and_predicate
{
    template <typename ArgT>
    enable_if<ice_and<is_callable<Predicate1,ArgT>::value,
                      is_callable<Predicate2,ArgT>::value>::value,
                      bool>::type operator()(const ArgT& arg)
    {
        return predicate1(arg) && predicate2(arg);
    }

    Predicate1 predicate1;
    Predicate2 predicate2;
};

template <typename Predicate1, typename Predicate2>
enable_if<ice_and<is_callable< Predicate1, boost::any >::value,
                  is_callable< Predicate2, boost::any >::value>::value,
                  and_predicate<Predicate1, Predicate2>>::type
operator&&(Predicate1 predicate1, Predicate2 predicate2)
{
    return and_predicate<Predicate1, Predicate2>{predicate1, predicate2};
}