Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
411 views
in Technique[技术] by (71.8m points)

c++11 - C++ metafunction to determine whether a type is callable

Is it possible to write a C++(0x) metafunction that determines whether a type is callable?

By callable type I mean a function type, function pointer type, function reference type (these are detected by boost::function_types::is_callable_builtin), lambda types, and any class with an overloaded operator() (and maybe any class with an implicit conversion operator to one of these, but that's not absolutely necessary).

EDIT: The metafunction should detect the presence of an operator() with any signature, including a templated operator(). I believe this is the difficult part.

EDIT: Here is a use case:

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 is what I would like to implement.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The presence of a non-templated T::operator() for a given type T can be detected by:

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)  )

The presence of a templated operator can be detected by:

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...

However, these two do not play nicely together, as decltype(&C::operator()) will produce an error if C has a templated function call operator. The solution is to run the sequence of checks against a templated operator first, and check for a regular operator() if and only if a templated one can not be found. This is done by specializing the non-templated check to a no-op if a templated one was found.

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

If the arity is always one, as discussed above, then the check should be simpler. I do not see the need for any additional type traits or library facilities for this to work.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...