Кодирование статических функций экземпляра метода батут с шаблонами

Я пытаюсь перекодировать довольно некрасивое шаблонное кодирование.

Для справки, оригинал находится здесь: https://codereview.stackexchange.com/questions/69545/recode-c-c-trampoline-function-macros-using-templates

class Final : Base
{
    void Foo(){...}
    void Bar(){...}

    static void init(){
        // register_method populates a table of "extern C" function pointers.
        register_method( "foo", & FooHandler );
        register_method( "bar", & BarHandler );
    }
    :
    // trampolines
    static void FooHandler( void* pInstance ) {
        Final* f = reinterpret_cast<Final*>(pInstance);
        f->Foo();
    }
    static void BarHandler( void* pInstance ) {
        Final* f = reinterpret_cast<Final*>(pInstance);
        f->Bar();
    }
}

Мой код взаимодействует с CPython (библиотека C). Среда выполнения Python видит "myInst.foo", ищет "foo" в таблице и вызывает:

Final::FooHandler( pointer_to_myInst );

(Обратите внимание, что можно типизировать статический метод к указателю на функцию C)

FooHandler батуты к методу Foo правильного экземпляра Final.

На самом деле дескриптор не так чист, и есть много методов, каждый из которых требует идентичного обработчика (но с отдельным адресом функции).

Я пытаюсь абстрагировать механизм обработчика в базовый класс, что-то вроде этого:

class Final : Base<Final>
{
    void Foo(){...}
    void Bar(){...}

    static void init(){
        // register_method populates a table of "extern C" function pointers.
        register_method( "foo", & Foo, Handler< &Foo> );
        register_method( "bar", & Bar, Handler< &Bar> );
    }
    :
}

class Base<Final>
{
    typedef void (Final::*FuncSig)(void);
    typedef void (Final::*HandlerSig)(void*); // takes 1 pvoid param

    void register_method( std::string name, FuncSig meth, HandlerSig handler ) {
        ...        
    }

    // generic trampoline
    template< Sig sig>
    static void Handler( void* pInstance ) {
        Final* f = reinterpret_cast<Final*>(pInstance);
        f ->* sig();
    }
}

В настоящее время я застреваю в ошибках компилятора ( http://ideone.com/vOtbcD), поэтому я даже не уверен, что метод верен.

Есть ли способ сделать это, или это один из тех случаев, когда вам действительно нужны макросы?

Для справки, оригинал находится здесь: https://codereview.stackexchange.com/questions/69545/recode-c-c-trampoline-function-macros-using-templates

Как видно, в оригинале используются довольно некрасивые макросы.

2 ответа

Решение

register_method должен быть статическим, если вы собираетесь вызывать его из статического init функция.

Для вызова функции-члена через указатель на функцию-член требуется дополнительный набор скобок: (f->*sig)();

После этого ваш тестовый пример компилируется в режиме C++11. Тем не менее, вы рассматривали возможность использования std::function а также std::bind сделать это вместо этого? Трудно сказать, как это будет выглядеть на самом деле, не зная, что register_method делает, но это может закончиться немного чище.

Следующий код работает на ideone ( http://ideone.com/vOtbcD):

#include <iostream>
using namespace std;

#include <map>

template<typename T>
class Base
{
public:    
    typedef void (T::*PFunc)(void);
    typedef void (*PHandler)(void*);

    using method_map_t = map< string, PHandler >;
    static method_map_t& methods( ) {
        static method_map_t* map_of_methods{};
        if( ! map_of_methods ) map_of_methods = new method_map_t;
        return *map_of_methods;
    }

    static void register_method( string name, PHandler handler ) {
        methods()[name] = handler;
    }

    // generic trampoline
    template<PFunc sig>
    static void Handler( void* pInstance ) {
        T* f = reinterpret_cast<T*>(pInstance);
        (f ->* sig)();
    }
};

...

class Final : Base<Final>
{
public:
    void Foo(){cout<<"got foo";}
    void Bar(){cout<<"got bar";}

    static void init(){
        // register_method populates a table of "extern C" function pointers.
        register_method( "foo", static_cast<PHandler>( &Handler<&Final::Foo> ) );
        register_method( "bar", static_cast<PHandler>( &Handler<&Final::Bar> ) );
    }
};

void InvokeFromC(void* inst, string name) {
    Base<Final>::PHandler h = Base<Final>::methods()[name];
    (*h)(inst);
}

int main() {
    Final* f = new Final{};
    f->init();
    // simulate invoking from C 
    InvokeFromC( f, "foo" );

    // your code goes here
    return 0;
}

Примечание: (Eelis на IRC) известно, что gcc не может разрешать адреса специализаций шаблонов функций, передаваемых функциям. Вы можете обойти это, либо объявив отдельные переменные для указателей на функции, либо используя грязное приведение (или хорошее приведение типа boost::implicit_cast)

Я разместил этот код на рассмотрение по адресу: https://codereview.stackexchange.com/questions/69876/static-to-instance-method-trampolining-with-templates

Другие вопросы по тегам