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

时间:2012-01-18 01:15:29

标签: c++ templates c++11 template-meta-programming sfinae

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

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

我希望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;
}

这会打印11,但我想要01,因为foo只定义了void operator()(const int &)

6 个答案:

答案 0 :(得分:9)

经过几个小时的游戏和一些serious discussions in the C++ chat room之后,我们终于得到了一个适用于可能重载或继承operator()的函子和基于@KerrekSB和@ BenVoigt版本的函数指针的版本。< / p>

#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...>{};

Live example on Ideone。请注意,两个失败的测试都是重载operator()测试。这是一个带有可变参数模板的GCC错误,已在GCC 4.7中修复。 Clang 3.1还将所有测试报告为passed

如果你希望operator()默认参数失败,有可能这样做,但是其他一些测试会在那时开始失败,我觉得尝试纠正它太麻烦了。

编辑:正如@Johannes在评论中正确指出的那样,我们在这里有点不一致,即functors which define a conversion to function pointer不会被检测为“可调用”。这是非常非常重要的修复,因此我不会打扰它(现在)。如果你绝对需要这个特性,那就留下评论,我会看到我能做些什么。


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

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

答案 1 :(得分: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;
}

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

答案 2 :(得分:2)

这样的事可能吗?它有点可以让它在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&>
{
}

答案 3 :(得分:2)

这是我讨厌的东西,可能是也可能不是你所需要的东西;它确实给(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
    ;
}

答案 4 :(得分:1)

这是一个可能的解决方案,它利用额外的测试来查看您的模板是否使用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;
}

答案 5 :(得分:0)

在做其他事情的同时,能够调整我的代码以适应。它具有与@Xeo相同的功能(和限制),但不需要sizeof trick / enable_if。默认参数代替需要执行enable_if来处理模板函数。我使用相同的测试代码Xeo写了

,在g ++ 4.7和clang 3.2下测试了它
#include <type_traits>
#include <functional>

namespace detail {
  template<typename T, class Args, class Enable=void>
  struct call_exact : std::false_type {};

  template<class...Args> struct ARGS { typedef void type; };

  template<class T, class ... Args, class C=T>
  C * opclass(decltype(std::declval<T>()(std::declval<Args>()...)) (C::*)(Args...)) { }
  template<class T, class ... Args, class C=T>
  C * opclass(decltype(std::declval<T>()(std::declval<Args>()...)) (C::*)(Args...) const) { }

  template<typename T, class ... Args>
  struct call_exact<T, ARGS<Args...>,
    typename ARGS<
       decltype(std::declval<T&>()(std::declval<Args>()...)),
       decltype(opclass<T, Args...>(&T::operator()))
     >::type
   > : std::true_type {};
}

template<class T, class ... Args>
struct Callable : detail::call_exact<T, detail::ARGS<Args...>> { };

template<typename R, typename... FArgs, typename... Args>
struct Callable<R(*)(FArgs...), Args...>
 : Callable<std::function<R(FArgs...)>, Args...>{};