Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I determine if a type is callable with only const references?

I want to write a C++ metafunction is_callable<F, Arg> that defines value to be true, if and only if the type F has the function call operator of the form SomeReturnType operator()(const Arg &). For example, in the following case

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

I want is_callable<foo, int &> to be false and is_callable<foo, const int &> to be true. This is what I have so far :

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

This prints 1 and 1, but I want 0 and 1 because foo only defines void operator()(const int &).

like image 500
keveman Avatar asked Jan 18 '12 01:01

keveman


4 Answers

After hours of playing around and some serious discussions in the C++ chat room, we finally got a version that works for functors with possibly overloaded or inherited operator() and for function pointers, based on @KerrekSB's and @BenVoigt's versions.

#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. Note that the two failing tests are overloaded operator() tests. This is a GCC bug with variadic templates, already fixed in GCC 4.7. Clang 3.1 also reports all tests as passed.

If you want operator() with default arguments to fail, there is a possible way to do that, however some other tests will start failing at that point and I found it as too much hassle to try and correct that.

Edit: As @Johannes correctly notes in the comment, we got a little inconsistency in here, namely that functors which define a conversion to function pointer will not be detected as "callable". This is, imho, pretty non-trivial to fix, as such I won't bother with it (for now). If you absolutely need this trait, well, leave a comment and I'll see what I can do.


Now that all this has been said, IMHO, the idea for this trait is stupid. Why whould you have such exact requirements? Why would the standard is_callable not suffice?

(Yes, I think the idea is stupid. Yes, I still went and built this. Yes, it was fun, very much so. No, I'm not insane. Atleast that's what I believe...)

like image 137
Xeo Avatar answered Nov 13 '22 07:11

Xeo


(with apologies to Kerrek for using his answer as a starting point)

EDIT: Updated to handle types without any operator() at all.

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

Demo: http://ideone.com/T3Iry

like image 44
Ben Voigt Avatar answered Nov 13 '22 06:11

Ben Voigt


Here's something I hacked up which may or may not be what you need; it does seem to give true (false) for (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
    ;
}
like image 2
Kerrek SB Avatar answered Nov 13 '22 07:11

Kerrek SB


Something like this maybe? It's a bit round about to make it work on 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&>
{
}
like image 2
ronag Avatar answered Nov 13 '22 06:11

ronag