2

I am trying to understand this code and I have found some more SO content on this topic.

In compact form:

#include <tuple>
namespace sqlite {
    namespace utility {
        template<typename> struct function_traits;

        template <typename Function>
        struct function_traits : public function_traits<
            decltype(&Function::operator())
        > {  };

        template <
            typename    ClassType,
            typename    ReturnType,
            typename... Arguments
        >
        struct function_traits<
            ReturnType(ClassType::*)(Arguments...) const
        > {
            typedef ReturnType result_type;

            template <std::size_t Index>
            using argument = typename std::tuple_element<
                Index,
                std::tuple<Arguments...>
            >::type;

            static const std::size_t arity = sizeof...(Arguments);

        };
    }
}

This led me to learn about the pointer-to-member-function feature of the language, and it's pretty clear to me at this point how to use the function traits (it's fairly straightforward that I can pull out not only the return type but the arity and even the types of the arguments) but I have gotten stuck in understanding why this works, or indeed why it is even possible for it to work...

Community
  • 1
  • 1
Steven Lu
  • 41,389
  • 58
  • 210
  • 364

3 Answers3

6

Let's break everything down piece by piece.

template<typename> struct function_traits;

That is the primary template declaration, declaring a template class with a single template parameter.

template <typename Function>
struct function_traits : public function_traits<
    decltype(&Function::operator())
> {  };

That is essentially a forwarding trait to allow functors (objects which have an operator()) to be used with this traits class. It's a definition of the primary template which inherits from the traits for operator(). This version will be selected when the specialization which is defined later is not matched.

template <
    typename    ClassType,
    typename    ReturnType,
    typename... Arguments
>
struct function_traits<
    ReturnType(ClassType::*)(Arguments...) const
>

That begins the definition of a partial specialization of function_traits for pointers to member functions. When you pass a member function pointer type as the template argument to function_traits, this specialization will be selected. The return type, class type and argument types will be deduced as template parameters.

    typedef ReturnType result_type;

That is a simple typedef which aliases the deduced ReturnType template parameter to result_type.

    template <std::size_t Index>
    using argument = typename std::tuple_element<
        Index,
        std::tuple<Arguments...>
    >::type;

That is what is called an alias template. When you provide an index such as function_traits<myFunc>::template argument<2>, it will collapse down to typename std::tuple_element<2, std::tuple<Arguments...>>::type. The goal of that is to extract the type of the argument at index Index. Rather than writing out all the template metaprogramming code to do that manually, the author chose to use the existing code in std::tuple. std::tuple_element extracts the type of the nth element of a tuple.

   static const std::size_t arity = sizeof...(Arguments);

sizeof... is used to get the size of a variadic template parameter pack, so this line stores the number of arguments for the function in the static integral member arity.

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
1

@tartanllama 's answer actually forgets to add std::remove_reference from the code example, which is necessary to cover all cases.

The template general case that handles lambdas should really be:

template <typename Function>
struct function_traits : public function_traits<
    decltype(&std::remove_reference_t<Function>::operator())
> {  };

If you're looking for a complete solution for all types in c++ that can be invoked, see my answer on a different SO question.

CygnusX1
  • 20,968
  • 5
  • 65
  • 109
Luka Govedič
  • 399
  • 4
  • 14
0

I did realize that the part including ClassType::* is meant to bind to decltype(&Function::operator()). I was learning about this construct (pointer to member function), but really having a tough time understanding why it's important for it to exist.

ReturnType(ClassType::*)(Arguments...) const is a template typename, in this case it is to be matched to a pointer to member function, and it is cleverly constructed in such a way as to be bound to the operator() member function, allowing the pattern matching to pull out the relevant types for us, those of the args and the return type. The ClassType would presumably become recognized as the same thing as the Function template parameter.

I was also confused by why a funny-looking templated using statement is required to look up the argument type values. I then realized that the Arguments... is a variadic template type construct which has to be tuple-ized using compile-time features and the whole business has nothing to do with "real" tuples. When I looked up tuple_element indeed it is intended as a tool for manipulating types at compile-time.

the remainder of this answer I am not sure about

I do wonder why pointer-to-member-function was used. I guess the reason could be that a regular function pointer or some such construct cannot actually be used to match the operator () member of functors, as a member function requires an instance of the object in order to become callable. This answer sheds some more light on this subject. I mostly understand it now I think.

Community
  • 1
  • 1
Steven Lu
  • 41,389
  • 58
  • 210
  • 364