Используйте функцию-член в качестве аргумента шаблона для создания оболочки stati c - PullRequest
0 голосов
/ 11 февраля 2020

Я пытаюсь написать оболочку c ++ 11 вокруг C API, и в основном есть способ регистрации уведомлений с помощью указателя функции stati c, который также возвращает мне «непрозрачный» указатель , которые предоставляются позже, в основном указатель на классы, которые я создаю, в этом примере class foo. По сути, я пытаюсь создать функцию-помощник stati c `` помощник <..> :: call, которая имеет сигнатуру API, но генерирует код для вызова моей функции-члена в экземпляре, созданном оболочкой c ++, и передается в через "непрозрачный" указатель вместе с ним. Эта функция stati c затем также преобразует аргументы при окончательном вызове функции-члена.

Мне кажется, что это почти работает, но у меня возникают проблемы с созданием более "хороших" пабликов. c функция register_handler в этом примере, которая скрывает "уродливые" внутренние органы. Это ошибка, которую я получаю:

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

1 Ответ

0 голосов
/ 11 февраля 2020

Простой ответ для 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");
}
...