Defining a traits type for callable objects

While playing around with lambda functions, and functors in general, I quickly discovered that in certain situations it would be very useful to obtain information about a functor’s return type, parameter types, and object type. However, perhaps do to some compiler quirks, the implementation was a bit trickier than I thought it ought to be.

For the final interface we would like to have something like the following.

auto foo = [](int a, bool b, double c) -> void {};
typedef callable_traits<decltype(foo)> traits;

//traits::object_t     is a compiler generated lambda type
//traits::result_t     is void
//traits::args_t       is std::tuple<int, bool, double>
//traits::arg<0>::type is int

So, as a first step, the interface.

template <typename T>
struct callable_traits {
    typedef unspecified object_t;
    typedef unspecified result_t;
    typedef unspecified args_t;

    template <unsigned N>
    struct arg : public std::tuple_element<N, args_t> {};
};

There are now two cases to deal with. (1) A functor (any object which defines operator()) and (2) A free function (pointer). So, we need to define a helper class to select between the cases.

//T is our potential function object; is_ptr is true_type when T is a pointer type
template <typename T, typename is_ptr = typename std::is_pointer<T>::type>
struct callable_helper;

//! specialization for functors (and lambdas)
template <typename T>
struct callable_helper<T, std::false_type> {
    typedef unspecified type;
};

//! specialization for function pointers
template <typename T>
struct callable_helper<T, std::true_type> {
    typedef unspecified  type;
};

Alright, so now we can determine whether we are dealing with a (potential) functor, or just a regular free function pointer. We next need to extract the required information for both cases: another helper class. This time we have three cases. (1) A free function pointer. (2) A non-const member pointer. (3) A const member pointer.

template <typename T>
struct callable_helper_ptr;

//! non-member functions
template <typename R, typename... Args>
struct callable_helper_ptr<R (*)(Args...)> {
    typedef void                object_t;
    typedef R                   result_t;
    typedef std::tuple<Args...> args_t;
};

//! member functions
template <typename R, typename O, typename... Args>
struct callable_helper_ptr<R (O::*)(Args...)> {
    typedef O                   object_t;
    typedef R                   result_t;
    typedef std::tuple<Args...> args_t;
};

//! const member functions
template <typename R, typename O, typename... Args>
struct callable_helper_ptr<R (O::*)(Args...) const> {
    typedef O                   object_t;
    typedef R                   result_t;
    typedef std::tuple<Args...> args_t;
};

Using template specialization we can select between the three cases, and with variadic templates we can capture the function parameters in a std::tuple. Gluing it all together and filling in the unspecified portions with our helper classes we arrive at the final implementation.

namespace detail {
    ////////////////////////////////////////////////////////////////////////////
    //! Select between function pointer types
    ////////////////////////////////////////////////////////////////////////////
    template <typename T>
    struct callable_helper_ptr;

    //! non-member functions
    template <typename R, typename... Args>
    struct callable_helper_ptr<R (*)(Args...)> {
        typedef void                object_t;
        typedef R                   result_t;
        typedef std::tuple<Args...> args_t;
    };

    //! member functions
    template <typename R, typename O, typename... Args>
    struct callable_helper_ptr<R (O::*)(Args...)> {
        typedef O                   object_t;
        typedef R                   result_t;
        typedef std::tuple<Args...> args_t;
    };

    //! const member functions
    template <typename R, typename O, typename... Args>
    struct callable_helper_ptr<R (O::*)(Args...) const> {
        typedef O                   object_t;
        typedef R                   result_t;
        typedef std::tuple<Args...> args_t;
    };

    ////////////////////////////////////////////////////////////////////////////
    //! Select between function pointers and functors
    ////////////////////////////////////////////////////////////////////////////
    template <typename T, typename is_ptr = typename std::is_pointer<T>::type>
    struct callable_helper;

    //! specialization for functors (and lambdas)
    template <typename T>
    struct callable_helper<T, std::false_type> {
        typedef callable_helper_ptr<decltype(&T::operator())> type;
    };

    //! specialization for function pointers
    template <typename T>
    struct callable_helper<T, std::true_type> {
        typedef callable_helper_ptr<T> type;
    };
} //namespace detail

////////////////////////////////////////////////////////////////////////////////
//! defines the various details of a callable object T
////////////////////////////////////////////////////////////////////////////////
template <typename T>
struct callable_traits {
    typedef typename detail::callable_helper<T>::type::object_t object_t;
    typedef typename detail::callable_helper<T>::type::result_t result_t;
    typedef typename detail::callable_helper<T>::type::args_t   args_t;

    template <unsigned N>
    struct arg : public std::tuple_element<N, args_t> {};
};

I’ll address what this might be useful for in a following post about smart pointers.

About these ads

1 Comment

Filed under C++, Programming

One response to “Defining a traits type for callable objects

  1. Pingback: A different way to work with exceptions: expected | Brandon's Blathering

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s