如何确定类型是否可以仅使用const引用进行调用?

kev*_*man 10 c++ templates sfinae template-meta-programming c++11

我想编写一个C++元函数is_callable<F, Arg>,定义valuetrue,当且仅当类型F具有表单的函数调用运算符时SomeReturnType operator()(const Arg &).例如,在以下情况中

struct foo {
  void operator(const int &) {}
};
Run Code Online (Sandbox Code Playgroud)

我想is_callable<foo, int &>成为falseis_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;
}
Run Code Online (Sandbox Code Playgroud)

这版画11,但我想01,因为foo只定义void operator()(const int &).

Xeo*_*Xeo 9

C++聊天室玩了几个小时并进行了一些认真的讨论后,我们终于得到了一个版本,适用于可能重载或继承的函子operator()和函数指针,基于@ KerrekSB和@ 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...>{};
Run Code Online (Sandbox Code Playgroud)

Ideone上的实例.请注意,两个失败的测试是重载operator()测试.这是具有可变参数模板的GCC错误,已在GCC 4.7中修复.Clang 3.1还将所有测试报告为passed.

如果你想让operator()默认参数失败,有一种可能的方法可以做到这一点,但是其他一些测试会在那时开始失败,我觉得尝试纠正它太麻烦了.

编辑:正如@Johannes在评论中正确注意到的那样,我们在这里有点不一致,即定义转换为函数指针的仿函数不会被检测为"可调用".这是非常非常重要的修复,因此我不会打扰它(现在).如果你绝对需要这个特性,那么,发表评论,我会看到我能做些什么.


既然已经说过这一切,恕我直言,这个特性的想法是愚蠢的.为什么你有这样的确切要求?为什么标准is_callable不够?

(是的,我认为这个想法是愚蠢的.是的,我仍然去构建这个.是的,它很有趣,非常如此.不,我不是疯了.至少我相信......)


Ben*_*igt 7

(向Kerrek道歉,以他的答案为出发点)

编辑:更新以处理类型,没有任何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;
}
Run Code Online (Sandbox Code Playgroud)

演示:http://ideone.com/T3Iry

  • 有趣的是,当使用[variadic templates](http://ideone.com/H1qLX)时,GCC 4.5.1报告了重载情况的"0".然而,使用Clang 3.1测试确认一切都确实正常,报告重载案例的"1".FWIW,我得到了一个版本[与函数指针一起工作](http://ideone.com/63g8S).请注意此处可变参数版本中的重载问题.它与Clang 3.1一起正常工作. (2认同)