Search code examples
functionc++11templatesmembersignature

Use member function as template argument to create a static wrapper


I'm trying to write a c++11 wrapper around a C API, and basically there is a way to register notifications with a static function pointer, which also passes me back an "opaque" pointer, which are provided at a later point, basically a pointer to classes I create, in this example the class foo. Basically, I'm trying to create a static function `helper<..>::call that has the API's signature, but generates code to call my member function on the instance that the c++ wrapper created, and is passed in through an "opaque" pointer along with it. This static function then also converts the arguments when finally calling the member function.

I seem to have this almost working, but I'm having trouble creating a "nicer" public function register_handler in this example, which hides the "uglier" internals. This is the error I'm getting:

test.cpp:154:37: error: no matching function for call to ‘register_handler<&foo::bar>(const char [6])’
  154 |  register_handler<&foo::bar>("test2"); // <-- trying to wrap it into a function so I can use only one template argument
      |                                     ^
test.cpp:137:6: note: candidate: ‘template<class T, class R, class ... Args, R (T::* Func)(Args ...)> void register_handler(const char*)’
  137 | void register_handler(const char* name)
      |      ^~~~~~~~~~~~~~~~

This is my test code:

#include <iostream>
#include <memory>
#include <vector>
#include <map>
#include <cassert>

// inspired by https://stackoverflow.com/a/7943765/2129246
template <typename T>
struct func_traits:
    public func_traits<decltype(&T::operator())>
{
};

template <typename R, typename... Args>
struct func_traits<R(*)(Args...)>
{
    enum { arity = sizeof...(Args) };

    typedef R result_type;

    using all_args = std::tuple<Args...>;

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

template <typename C, typename R, typename... Args>
struct func_traits<R(C::*)(Args...) const>
{
    enum { arity = sizeof...(Args) };

    typedef C class_type;
    typedef R result_type;

    using all_args = std::tuple<Args...>;

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

template< std::size_t... Ns >
struct indices {
    typedef indices< Ns..., sizeof...( Ns ) > next;
};

template< std::size_t N >
struct make_indices {
    typedef typename make_indices< N - 1 >::type::next type;
};

template<>
struct make_indices< 0 > {
    typedef indices<> type;
};


struct value
{
    std::string str_;

    template <typename T>
    value(T val):
        str_(std::to_string(val))
    {
    }

    value(const char* str):
        str_(str)
    {
    }

    value(const std::string& str):
        str_(str)
    {
    }

    operator int() const
    {
        return std::stoi(str_);
    }

    operator double() const
    {
        return std::stof(str_);
    }

    operator std::string() const
    {
        return str_;
    }
};

std::map<std::string, void(*)(void*, const std::vector<value>&)> g_handlers;

template <typename T, T>
struct helper;

template <typename T, typename R, typename... Args, R(T::*Func)(Args...)>
struct helper<R(T::*)(Args...), Func>
{
    template <size_t... Is>
    static void expand(T* obj, const std::vector<value>& args, indices<Is...>)
    {
        assert(sizeof...(Is) <= args.size());
        (obj->*Func)((args[Is])...);
    }

    static void call(void *p, const std::vector<value>& args)
    {
        T* obj = reinterpret_cast<T*>(p);
        expand(obj, args, typename make_indices<sizeof...(Args)>::type());
    }

    static void reg_handler(const char* name)
    {
        g_handlers.insert(std::make_pair(name, call));
    };
};

template <typename Obj>
void call_handler(Obj& obj, const char* name, const std::vector<value>& args)
{
    auto it = g_handlers.find(name);
    if (it != g_handlers.end())
        it->second(reinterpret_cast<void*>(&obj), args);
    else
        std::cout << "handler not registered: " << name << std::endl;
}

// The code below somehow doesn't ever match this template
template <typename T, typename R, typename... Args, R(T::*Func)(Args...)>
void register_handler(const char* name)
{
    helper<R(T::*)(Args...), Func>::reg_handler(name);
}

struct foo
{
    void bar(int v, const std::string& str, double f)
    {
        std::cout << "bar: v=" << v << " str=" << str << " f=" << f << std::endl;
    };
};

int main()
{
    // register member function handlers before we have any instances
    helper<decltype(&foo::bar), &foo::bar>::reg_handler("test"); // <-- works, but "ugly" and exposes internal implementation
    register_handler<&foo::bar>("test2"); // <-- trying to wrap it into a function so I can use only one template argument

    // now we have an instance
    foo f;

    // call the previously registered handler
    call_handler(f, "test", {1, "2", 3.45});
    call_handler(f, "test2", {1, "2", 3.45});
    return 0;
}

Solution

  • The simple answer for C++11 is: You can't!

    From C++17 you are able to use auto also for non type template parameters as a function pointer or member function pointer is not a type here and you have no syntax to describe your function pointer type.

    In C++17 you can use it like this:

    struct foo 
    {   
        void bar(){}
    };  
    
    template <typename T, T>
    struct helper;
    
    template <typename T, typename R, typename... Args, R(T::*Func)(Args...)>
    struct helper<R(T::*)(Args...), Func>
    {   
        static void reg_handler(const char* name)
        {
            // ...  here your code continues
        }   
    };
    
    template < auto T > 
    struct X
    {   
    };  
    
    template <typename T, typename R, typename... Args, R(T::*Func)(Args...)>
    struct X<Func>
    {   
        static void register_handler( const char* name )
        {   
            helper<R(T::*)(Args...), Func>::reg_handler(name);
        }   
    };  
    
    int main()
    {
        X<&foo::bar>::register_handler("check");
    }