Как определить, может ли тип вызываться только с помощью константных ссылок? - PullRequest
10 голосов
/ 18 января 2012

Я хочу написать метафункцию C ++ is_callable<F, Arg>, которая определяет value как true, если и только если тип F имеет оператор вызова функции в форме SomeReturnType operator()(const Arg &).Например, в следующем случае

struct foo {
  void operator(const int &) {}
};

я хочу, чтобы is_callable<foo, int &> было false и is_callable<foo, const int &> было true.Это то, что у меня есть:

#include <memory>
#include <iostream>

template<typename F, typename Arg>
struct is_callable {
private:

  template<typename>
  static char (&test(...))[2];

  template<unsigned>
  struct helper {
    typedef void *type;
  };

  template<typename UVisitor>
  static char test(
               typename helper<
                 sizeof(std::declval<UVisitor>()(std::declval<Arg>()), 0)
                 >::type
               );
public:
  static const bool value = (sizeof(test<F>(0)) == sizeof(char));
};

struct foo {
  void operator()(const int &) {}
};

using namespace std;

int main(void)
{
  cout << is_callable<foo, int &>::value << "\n";
  cout << is_callable<foo, const int &>::value << "\n";

  return 0;
}

Это печатает 1 и 1, но я хочу 0 и 1, потому что foo определяет только void operator()(const int &).

Ответы [ 6 ]

9 голосов
/ 18 января 2012

После нескольких часов тренировок и некоторых серьезных обсуждений в чате C ++ , мы наконец-то получили версию, которая работает для функторов с возможно перегруженными или унаследованными operator() и для указателей на функции, основанные на @ KerrekSB'sи версии @ BenVoigt.

#include <utility>
#include <type_traits>

template <typename F, typename... Args>
class Callable{
  static int tester[1];
  typedef char yes;
  typedef yes (&no)[2];

  template <typename G, typename... Brgs, typename C>
  static typename std::enable_if<!std::is_same<G,C>::value, char>::type
      sfinae(decltype(std::declval<G>()(std::declval<Brgs>()...)) (C::*pfn)(Brgs...));

  template <typename G, typename... Brgs, typename C>
  static typename std::enable_if<!std::is_same<G,C>::value, char>::type
      sfinae(decltype(std::declval<G>()(std::declval<Brgs>()...)) (C::*pfn)(Brgs...) const);

  template <typename G, typename... Brgs>
  static char sfinae(decltype(std::declval<G>()(std::declval<Brgs>()...)) (G::*pfn)(Brgs...));

  template <typename G, typename... Brgs>
  static char sfinae(decltype(std::declval<G>()(std::declval<Brgs>()...)) (G::*pfn)(Brgs...) const);

  template <typename G, typename... Brgs>
  static yes test(int (&a)[sizeof(sfinae<G,Brgs...>(&G::operator()))]);

  template <typename G, typename... Brgs>
  static no test(...);

public:
  static bool const value = sizeof(test<F, Args...>(tester)) == sizeof(yes);
};

template<class R, class... Args>
struct Helper{ R operator()(Args...); };

template<typename R, typename... FArgs, typename... Args>
class Callable<R(*)(FArgs...), Args...>
  : public Callable<Helper<R, FArgs...>, Args...>{};

Живой пример на Ideone .Обратите внимание, что два провальных теста перегружены operator() тестами.Это ошибка GCC с вариационными шаблонами, уже исправленная в GCC 4.7.Clang 3.1 также сообщает обо всех тестах как passed.

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

Редактировать: Как правильно заметил в комментарии @Johannes, у нас возникло небольшое несоответствие, а именно, что функторы, которыеопределение преобразования в указатель на функцию не будет определяться как «вызываемый».Это, имхо, довольно нетривиально исправить, поэтому я не буду беспокоиться об этом (пока).Если вам абсолютно нужна эта черта, оставьте комментарий, и я посмотрю, что я могу сделать.


Теперь, когда все это было сказано, ИМХО , идея для этой черты глупа.Почему у вас такие точные требования?Почему стандарт is_callable не достаточно?

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

7 голосов
/ 18 января 2012

(с извинениями перед Керреком за использование его ответа в качестве отправной точки)

РЕДАКТИРОВАТЬ: Обновлено для обработки типов без каких-либо operator() вообще.

#include <utility>

template <typename F, typename Arg>
struct Callable
{
private:
  static int tester[1];
  typedef char                      yes;
  typedef struct { char array[2]; } no;

  template <typename G, typename Brg>
  static char sfinae(decltype(std::declval<G>()(std::declval<Brg>())) (G::*pfn)(Brg)) { return 0; }

  template <typename G, typename Brg>
  static char sfinae(decltype(std::declval<G>()(std::declval<Brg>())) (G::*pfn)(Brg) const) { return 0; }

  template <typename G, typename Brg>
  static yes test(int (&a)[sizeof(sfinae<G,Brg>(&G::operator()))]);

  template <typename G, typename Brg>
  static no test(...);

public:
  static bool const value = sizeof(test<F, Arg>(tester)) == sizeof(yes);
};

struct Foo
{
  int operator()(int &) { return 1; }

};

struct Bar
{
  int operator()(int const &) { return 2; }
};

struct Wazz
{
  int operator()(int const &) const { return 3; }
};

struct Frob
{
  int operator()(int &) { return 4; }
  int operator()(int const &) const { return 5; }
};

struct Blip
{
  template<typename T>
  int operator()(T) { return 6; }
};

struct Boom
{

};

struct Zap
{
  int operator()(int) { return 42; }
};

#include <iostream>
int main()
{
  std::cout << "Foo(const int &):  " << Callable<Foo,  int const &>::value << std::endl
            << "Foo(int &):        " << Callable<Foo,  int &>::value << std::endl
            << "Bar(const int &):  " << Callable<Bar,  const int &>::value << std::endl
            << "Bar(int &):        " << Callable<Bar,  int &>::value << std::endl
            << "Zap(const int &):  " << Callable<Zap , const int &>::value << std::endl
            << "Zap(int&):         " << Callable<Zap , int &>::value << std::endl
            << "Wazz(const int &): " << Callable<Wazz, const int &>::value << std::endl
            << "Wazz(int &):       " << Callable<Wazz, int &>::value << std::endl
            << "Frob(const int &): " << Callable<Frob, const int &>::value << std::endl
            << "Frob(int &):       " << Callable<Frob, int &>::value << std::endl
            << "Blip(const int &): " << Callable<Blip, const int &>::value << std::endl
            << "Blip(int &):       " << Callable<Blip, int &>::value << std::endl
            << "Boom(const int &): " << Callable<Boom, const int &>::value << std::endl
            << "Boom(int&):        " << Callable<Boom, int &>::value << std::endl;
}

Демо: http://ideone.com/T3Iry

2 голосов
/ 18 января 2012

Вот что я взломал, что может или не может быть то, что вам нужно; похоже, он дает истинное (ложное) значение для (const) int & ...

#include <utility>

template <typename F, typename Arg>
struct Callable
{
private:
  typedef char                      yes;
  typedef struct { char array[2]; } no;

  template <typename G, typename Brg>
  static yes test(decltype(std::declval<G>()(std::declval<Brg>())) *);

  template <typename G, typename Brg>
  static no test(...);

public:
  static bool const value = sizeof(test<F, Arg>(nullptr)) == sizeof(yes);
};

struct Foo
{
  int operator()(int &) { return 1; }
  // int operator()(int const &) const { return 2; } // enable and compare
};

#include <iostream>
int main()
{
  std::cout << "Foo(const int &): " << Callable<Foo, int const &>::value << std::endl
            << "Foo(int &):       " << Callable<Foo, int &>::value << std::endl
    ;
}
2 голосов
/ 18 января 2012

Нечто подобное может быть? Немного об этом поработает на VS2010.

template<typename FPtr>
struct function_traits_impl;

template<typename R, typename A1>
struct function_traits_impl<R (*)(A1)>
{
    typedef A1 arg1_type;
};

template<typename R, typename C, typename A1>
struct function_traits_impl<R (C::*)(A1)>
{
    typedef A1 arg1_type;
};

template<typename R, typename C, typename A1>
struct function_traits_impl<R (C::*)(A1) const>
{
    typedef A1 arg1_type;
};

template<typename T>
typename function_traits_impl<T>::arg1_type arg1_type_helper(T);

template<typename F>
struct function_traits
{
    typedef decltype(arg1_type_helper(&F::operator())) arg1_type;
};

template<typename F, typename Arg>
struct is_callable : public std::is_same<typename function_traits<F>::arg1_type, const Arg&>
{
}
1 голос
/ 18 января 2012

Вот возможное решение, которое использует дополнительный тест, чтобы увидеть, создается ли ваш шаблон с const T&:

#include <memory>
#include <iostream>

using namespace std;

template<typename F, typename Arg>
struct is_callable {
private:

  template<typename>
  static char (&test(...))[2];

  template<bool, unsigned value>
  struct helper {};

  template<unsigned value>
  struct helper<true, value> {
    typedef void *type;
  };

  template<typename T>
  struct is_const_ref {};

  template<typename T>
  struct is_const_ref<T&> {
    static const bool value = false;
  };

  template<typename T>
  struct is_const_ref<const T&> {
    static const bool value = true;
  };

  template<typename UVisitor>
  static char test(typename helper<is_const_ref<Arg>::value, 
                                   sizeof(std::declval<UVisitor>()(std::declval<Arg>()), 0)>::type);
public:
  static const bool value = (sizeof(test<F>(0)) == sizeof(char));
};

struct foo {
  void operator()(const int &) {}
};

int main(void)
{
  cout << is_callable<foo, int &>::value << "\n";
  cout << is_callable<foo, const int &>::value << "\n";

  return 0;
}
0 голосов
/ 29 марта 2013

Пробежал через это, делая что-то еще, смог адаптировать мой код, чтобы соответствовать.Он имеет те же функции (и ограничения), что и @Xeo, но не требует sizeof trick / enable_if.Параметр по умолчанию заменяет enable_if для обработки шаблонных функций.Я протестировал его под g ++ 4.7 и clang 3.2, используя тот же тестовый код, который Xeo записал

...