Search code examples
c++c++11templatesfunction-pointersfunction-templates

Template definition to wrap any callable: class member functions, among others


I looked up to this and this SO answers, but none solves my problem.

Short description: the template function, where much more should happen than just that of this example, should be able to wrap any other function, also function members of a class.

Minimal working example:

#include <iostream>

template <typename T, typename ...ArgsT>
T wrapper(T(*func)(ArgsT...), ArgsT... args)
{
    return func(args...);
}

class TestClass
{
public:
    int mult_by_2_member(int a)
    {
        return a * 2;
    }

    static int mult_by_2_static(int a)
    {
        return a * 2;
    }

    void wrap_funcs()
    {
        // std::cout << "Wrapped member = " 
        //           << wrapper(&mult_by_2_member, 2)  // Prob: cannot convert `int(TestClass::*)(int) to int(*)(int)`
        //           << std::endl;
        std::cout << "Wrapped static = " << wrapper(mult_by_2_static, 2) << std::endl;
    }
};

int main()
{
    TestClass test_instance;
    test_instance.wrap_funcs();
    // skipping wrapping of a global function... that works correctly
}

The error message, as posted in the snippet above, is obvious. But I did not yet figure out how to solve it, or even whether the whole starting logic or intent is valid.

Any solution suggestions or sources of information?


EDIT

A possible solution would be:

/* as above */
static int mult_by_2_static(TestClass* p, int a)
{
   return p->mult_by_2_member(2);
}

void wrap_funcs()
{
    // std::cout << "Wrapped member = " << wrapper(&mult_by_2_member, 2) << " = " << std::endl;
    std::cout << "Wrapped static = " << wrapper(mult_by_2_static, this, 2) << std::endl;
}

But then I would have to "pre-wrap" all target member functions into static ones, which is a lot of boilerplate.


Solution

  • Edit: With C++17 JeJo's approach actually is superior! Leaving this variant here only for the case of only older standards being available – and for documenting how member function pointers work, if ever needed in another scenario...

    The problem here is that member functions have a different signature (including calling convention) than static member functions – from compiler point of view the latter are just ordinary functions that are tied to the scope of a class, possibly with access restrictions.

    Your template function is capable to accept ordinary or static member functions, but not non-static member functions, as you noticed already – and there's a further problem with member functions: You need an additional instance of the type to call the member function on, so you'd need something like:

    template <typename R, typename T, typename ... A>
    R wrapper(T& objectToCallOn, <WhateverSyntax> memberFunctionPointer, A... a)
    {
       return objectToCallOn.memberFunctionPointer(a...);
       //                   ^ actually requires different syntax!
    } 
    

    This makes obvious that you need two different templates, the one you have already and the one for member function pointers. Correct syntax for is:

    template <typename R, typename T, typename ... A>
    R wrapper(T& objectToCallOn, R (T::*memberFunctionPointer)(A ...), A... a)
    {
       return (objectToCallOn.*memberFunctionPointer)(a...);
    }
    

    Note that .* operator (as well as its analog for pointers, ->* operator) has lower precedence than the function call operator, thus the parentheses cannot be left out.

    Now needing two templates with the identical implementation, apart from the (member) function call likely is not what you'd like to have...

    My proposal to solve is transforming the original template into a more generic form (from here on relying on C++11):

    template <typename F, typename ...ArgsT>
    auto wrapper(F& func, ArgsT... args) -> decltype(func(args...))
    {
        return func(args...);
    }
    

    This way you can call the template with arbitrary callable objects, being them functions or callable objects (functors).

    Now you can call it with the static function just as you had before or e.g. with a lambda doing the member function call like:

    wrapper([&theObject](int value) { theObject.mult_by_2_member(value); }, 2);
    

    If you want to avoid the inconvenience of having to write the lambda you can still write the template overload above and hide the lambda away within:

    template <typename R, typename T, typename ... A>
    R wrapper(T& t, R (T::*f)(A ...), A... a)
    {
       return wrapper([&t, &f](A ... a) { (t.*f)(a...); }, a...);
    }
    

    All code entirely untested, if you find a bug, please fix yourself.

    Side note: You might want to implement perfect forwarding for your wrapper function...