使用成员函数作为模板参数来创建静态包装器

Use member function as template argument to create a static wrapper

我正在尝试围绕 C API 编写一个 c++11 包装器,基本上有一种方法可以使用静态函数指针注册通知,它还会传回一个 [=22] =] 指针,稍后会提供,基本上是指向我创建的 类 的指针,在本例中为 class foo。基本上,我正在尝试创建一个静态函数`helper<..>::call,它具有 API 的签名,但生成代码以在 c++ 包装器创建的实例上调用我的成员函数,并且通过 "opaque" 指针与其一起传入。然后这个静态函数在最终调用成员函数时也会转换参数。

我似乎几乎 可以正常工作,但在本例中我无法创建 "nicer" public 函数 register_handler ,它隐藏了 "uglier" 内部结构。这是我收到的错误:

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)
      |      ^~~~~~~~~~~~~~~~

这是我的测试代码:

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

// inspired by 
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;
}

C++11 的简单答案是:你不能!

从 C++17 开始,您也可以将 auto 用于非类型模板参数,因为函数指针或成员函数指针不是此处的类型,并且您没有语法来描述您的函数指针类型.

在 C++17 中你可以这样使用它:

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");
}