Search code examples
c++templatesc++17pointer-to-memberfunction-templates

Template accepting all member function pointers (including CV-qualified and ref-qualified)


I want to write a template which accepts a pointer to a member function (possibly CV-qualified and/or ref-qualified) while also matching all the relevant types (return value's type, class type and types of the arguments).

Simple version could look like:

template <typename ReturnValue, typename Class, typename... Arguments>
void foo(ReturnValue(Class::* function_ptr)(Arguments...))
{
    // do something with |function_ptr|
}

This works nicely for non-CV-qualified non-ref-qualified member function pointers, but fails to match with pointers to CV-qualified and/or ref-qualified member functions.

Is there a nice way to keep this in a single template and handle all cases by nicely deducing qualifiers somehow?


I could downgrade to a simple template that accepts everything (possibly with some SFINAE around std::is_member_function_pointer). But I would still need to extract the types (return, class and arguments) somewhere, so that would not actually save me any work.


Solution

  • How about pass the member function pointer to the foo and using a helper trait we retrieve the types of class, return, and arguments!

    Provide a trait something like:

    template<typename Class> struct class_traits final {};
    template<typename ReType, typename Class, typename... Args>
    struct class_traits<ReType(Class::*)(Args...)> final
    {
        using class_type = Class;
        using ret_type = ReType;
    };    
    
    // traits helpers
    template<typename MemFunctionPtr> using  class_type = typename class_traits<MemFunctionPtr>::class_type;
    template<typename MemFunctionPtr> using  ret_type = typename class_traits<MemFunctionPtr>::ret_type;
    

    Now in the foo

    #include <functional> // std::invoke
    
    template <typename MemFuncType, typename... Args>
    void foo(MemFuncType&& func, Args&&... args) 
    {
        // class type can be retrieved!
        class_type<MemFuncType> obj{};
    
        // types other than void!!
        ret_type<MemFuncType> res = std::invoke(func, obj, std::forward<Args>(args)...); 
    
        // do something with |function_ptr|
    
    }
    

    Now the main-part of the story. The above will only resolve the non-const, non-volatile, and non-noexcept, etc versions of the member function pointers. Exactly what you asked is missing!

    For the rest, we need to provide different traits. Inspired from this post, we can do it by macro, which will do the boilerplate duplications of the traits for us:

    template<typename Class> struct class_traits final {};
    #define CREATE_CLASS_TRAITS(...) \
    template<typename ReType, typename Class, typename... Args> \
    struct class_traits<ReType(Class::*)(Args...)__VA_ARGS__> final  \
    { \
        using class_type = Class; \
        using ret_type = ReType; \
    }
    
    CREATE_CLASS_TRAITS();
    CREATE_CLASS_TRAITS(const);
    CREATE_CLASS_TRAITS(volatile);
    CREATE_CLASS_TRAITS(const volatile);
    CREATE_CLASS_TRAITS(&);
    CREATE_CLASS_TRAITS(const&);
    CREATE_CLASS_TRAITS(volatile&);
    CREATE_CLASS_TRAITS(const volatile&);
    CREATE_CLASS_TRAITS(&&);
    CREATE_CLASS_TRAITS(const&&);
    CREATE_CLASS_TRAITS(volatile&&);
    CREATE_CLASS_TRAITS(const volatile&&);
    CREATE_CLASS_TRAITS(noexcept);
    CREATE_CLASS_TRAITS(const noexcept);
    CREATE_CLASS_TRAITS(volatile noexcept);
    CREATE_CLASS_TRAITS(const volatile noexcept);
    CREATE_CLASS_TRAITS(&noexcept);
    CREATE_CLASS_TRAITS(const& noexcept);
    CREATE_CLASS_TRAITS(volatile& noexcept);
    CREATE_CLASS_TRAITS(const volatile& noexcept);
    CREATE_CLASS_TRAITS(&& noexcept);
    CREATE_CLASS_TRAITS(const&& noexcept);
    CREATE_CLASS_TRAITS(volatile&& noexcept);
    CREATE_CLASS_TRAITS(const volatile&& noexcept);
    #undef CREATE_CLASS_TRAITS
    
    // traits helpers
    template<typename MemFunctionPtr> using  class_type = typename class_traits<MemFunctionPtr>::class_type;
    template<typename MemFunctionPtr> using  ret_type = typename class_traits<MemFunctionPtr>::ret_type;
    

    Now we can:

    template <typename MemFuncType, typename... Args>
    void foo(MemFuncType&& func, Args&&... args) 
    {
        class_type<MemFuncType> obj{};
    
        using Type = decltype(std::invoke(func, obj, std::forward<Args>(args)...));
        static_assert(std::is_same_v<ret_type<MemFuncType>, Type>, "are not same");
    
        std::invoke(func, obj, std::forward<Args>(args)...);
    
        // do something with |function_ptr|
    }
    
    class MyClass
    {
    public:
        void func() { std::cout << "MyClass::void func()\n"; }
        void func1() const { std::cout << "MyClass::void func1() const\n"; }
        void func2() const noexcept{ std::cout << "MyClass::void func2() const noexcept\n"; }
        int func3() const { std::cout << "MyClass::func3() const\n";  return {}; }
        int func4(int a, double b) const { std::cout << "MyClass::func3() const"<< a << " " << b << "\n";  return {}; }
    };
    
    int main()
    {
        foo(&MyClass::func);
        foo(&MyClass::func1);
        foo(&MyClass::func2);
        foo(&MyClass::func3);
        foo(&MyClass::func4, 1, 2.);
        return 0;
    }
    

    (See Live Demo Online)