edorado
edorado

Reputation: 375

C++ template mechanism to get the number of function arguments, which would work for lambdas and normal functions

I am trying to implement a mechanism, which would provide me some information about the template argument function type. Main idea is to get the number of arguments, return type, total sum of sizes of each argument. I have something running for lambdas based on this entry.

template <typename T, typename... Args>
struct sumSizeOfArgs {
    enum { totalSize = sizeof(typename std::decay<T>::type) + sumSizeOfArgs<Args...>::totalSize };
};

template<typename T>
struct sumSizeOfArgs<T> {
    enum  {totalSize = sizeof(typename std::decay<T>::type)};
};
}             
template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{};

template <typename ClassType, typename ReturnType, typename... Args>

struct function_traits<ReturnType(ClassType::*)(Args...) const>
{
  enum { arity = sizeof...(Args) };

  typedef ReturnType result_type;

enum { totalSize = sumSizeOfArgs<Args...>::totalSize };

template <size_t i>
struct arg
{
    typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
};
};

However, indeed this code does not work for normal functions types such as

 function_traits<void()>

it does not have an operator(). I would greatly appreciate, any suggestion to make this code work for both cases. Thanks,

Upvotes: 3

Views: 1077

Answers (1)

Piotr Skotnicki
Piotr Skotnicki

Reputation: 48447

Slightly improved original part:

#include <tuple>
#include <type_traits>

template <typename... Args>
struct sumSizeOfArgs
{
    static constexpr size_t totalSize = 0;
};

template <typename T, typename... Args>
struct sumSizeOfArgs<T, Args...>
{
    static constexpr size_t totalSize = sizeof(typename std::decay<T>::type)
                                        + sumSizeOfArgs<Args...>::totalSize;
};

template <typename T>
struct sumSizeOfArgs<T>
{
    static constexpr size_t totalSize = sizeof(typename std::decay<T>::type);
};

template <typename T>
struct function_traits_impl;

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...)>
{
    static constexpr size_t arity = sizeof...(Args);

    using result_type = ReturnType;

    static constexpr size_t totalSize = sumSizeOfArgs<Args...>::totalSize;

    template <size_t i>
    struct arg
    {
        using type = typename std::tuple_element<i, std::tuple<Args...>>::type;
    };
};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const>
    : function_traits_impl<ReturnType(ClassType::*)(Args...)> {};

New part starts here (traits' class partial specialization for functions):

template <typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(Args...)>
{
    static constexpr size_t arity = sizeof...(Args);

    using result_type = ReturnType;

    static constexpr size_t totalSize = sumSizeOfArgs<Args...>::totalSize;

    template <size_t i>
    struct arg
    {
        using type = typename std::tuple_element<i, std::tuple<Args...>>::type;
    };
};

template <typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(*)(Args...)>
    : function_traits_impl<ReturnType(Args...)> {};

Crucial part is here (determining the presence of operator()):

template <typename T, typename V = void>
struct function_traits
    : function_traits_impl<T> {};

template <typename T>
struct function_traits<T, decltype((void)&T::operator())>
    : function_traits_impl<decltype(&T::operator())> {};

Test:

int main()
{
    static_assert(function_traits<void()>::arity == 0, "!");

    static_assert(function_traits<void(int)>::arity == 1, "!");

    static_assert(function_traits<void(*)(int, float)>::arity == 2, "!");  

    auto lambda = [] (int, float, char) {};
    static_assert(function_traits<decltype(lambda)>::arity == 3, "!");

    auto mutable_lambda = [] (int, float, char, double) mutable {};
    static_assert(function_traits<decltype(mutable_lambda)>::arity == 4, "!");
}

DEMO 1


Or even simpler, with a single trait class:

#include <tuple>

template <typename ReturnType, typename... Args>
struct function_traits_defs
{
    static constexpr size_t arity = sizeof...(Args);

    using result_type = ReturnType;

    template <size_t i>
    struct arg
    {
        using type = typename std::tuple_element<i, std::tuple<Args...>>::type;
    };
};

template <typename T>
struct function_traits_impl;

template <typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(Args...)>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(*)(Args...)>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...)>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const>
    : function_traits_defs<ReturnType, Args...> {};

// + other cv-ref-variations

template <typename T, typename V = void>
struct function_traits
    : function_traits_impl<T> {};

template <typename T>
struct function_traits<T, decltype((void)&T::operator())>
    : function_traits_impl<decltype(&T::operator())> {};

int main()
{
    static_assert(function_traits<void()>::arity == 0, "!");

    static_assert(function_traits<void(int)>::arity == 1, "!");

    static_assert(function_traits<void(*)(int, float)>::arity == 2, "!");  

    auto lambda = [] (int, float, char) {};
    static_assert(function_traits<decltype(lambda)>::arity == 3, "!");

    auto mutable_lambda = [] (int, float, char, double) mutable {};
    static_assert(function_traits<decltype(mutable_lambda)>::arity == 4, "!");

    struct Functor
    {
        void operator()(int) {}
    };
    static_assert(function_traits<Functor>::arity == 1, "!");
}

DEMO 2


Note: Unfortunately none of the above solutions will work for generic lambdas, related discussion is Arity of a generic lambda

Upvotes: 4

Related Questions