Forum Programmation.c++ Passer un pointeur de membre de classe à une fonction statique ou une lambda sans capture?

Posté par . Licence CC by-sa.
0
13
mar.
2019

Bonjour.

Je suis en train de me faire la main sur l'interfaçage de Lua avec le C++. J'ai compris qu'il existe une tripotée de bibliothèques pour faire ça mais je voudrais profiter de l'occasion pour affûter ma pratique des patrons en C++ sur la résolution d'un casse-tête (cherchez pas, j'aime bien ça).

Le contexte

L'idée que je poursuis est de passer un pointeur vers un membre d'une classe quelconque à une fonction statique ou une lambda (mais celle-ci doit être sans capture). J'ai défini une classe C++ qui sert de modèle pour le passage d'objets de type user data. Les fonctions Lua que j'ai définies récupèrent un pointeur vers l'instance en vue d'appeler une fonction membre de cette instance.

Voici un exemple de classe C++:

// Classe C++ à utiliser en tant que user_data
class person
{
private:
    const std::string name;
    int age;

public:
    person(const char* n) : name(n) {}
    person(const char* n, int a) : name(n), age(a) {}

    ~person() { fprintf(stderr, "Destroying person instance %p ...\n", this); }

    void print() const  { printf("%s is %i\n", name.c_str(), age); }
    int getAge() const  { return age; }
    void setAge(int a)  { age = a; }
};

Les trois méthodes que je publie dans Lua sont "print", "getAge" et "setAge". Ces trois méthodes sont appelées depuis des fonctions (à la signature) Lua enregistrées via luaL_register(), qui demande un tableau d'éléments de type luaL_Reg.

J'ai défini un patron qui marche très bien, qui récupère le pointeur vers l'instance de la classe depuis la pile, appelle la fonction en prenant chacun de ses arguments dans la pile et retourne le résultat dans la pile.

Mon casse-tête est la manière de déclarer mon tableau d'éléments luaL_Reg:

/// Ugly way to create a list of registered functions... (waiting for better)
#define lua_reg(name, func) \
    luaL_Reg{name,  [](lua::stateptr_t L) { return lua::forward(L, (func)); } }

...

// Création d'un type user_data pour la classe person:
    Lua.user_type<person, const char*, int>(
        "Person",
        lua_reg("print", &person::print),
        lua_reg("getAge", &person::getAge),
        lua_reg("setAge", &person::setAge)
    );

Dans cet exemple je me sers d'une fonction lambda comme fonction inscrite. J'aurais pu me servir d'une fonction statique mais ça ne change pas le casse-tête.

Le casse-tête

Je voudrais faire ça:

template </* n'importe quoi de possible */>
luaL_Reg make_reg(const char* name, /* quelque chose qui désigne une fonction membre de person*/) 
{
    return {
        name,
        [ /* une lambda sans capture */](lua_State* L) { return lua::forward(L, /* la fonction membre */); }
    }
}

Le casse-tête est ceci:

  • je peux passer un pointeur vers une fonction membre en tant que paramètre à une fonction et profiter de la déduction automatique des types/arguments, puis utiliser une lambda mais alors celle-ci doit capturer la valeur du paramètre… ce qui donne une signature incompatible avec le type lua_cfunction.

  • je peux utiliser un argument de patron pour passer le pointeur vers la fonction membre et ça marche très bien… sauf que c'est plutôt casse-bonbons. Exemple:

template <class U>
struct forwarder
{
    //~ template <typename T, T (U::*function)(void) const>
    //~ static int __f(lua::stateptr_t L) { return lua::forward(L, function); }
    template <typename T, T function>
    static int __f(lua::stateptr_t L) { return lua::forward(L, function); }
};

lua_CFunction test = forwarder<person>::__f<decltype(&person::print), &person::print>;

Le plus simple que je sois arrivé à écrire demande de me taper un decltype(…) pour le premier argument du patron. Au final, j'appellerais bien une macro pour faire ça, ce qui est exactement ce que je souhaitais éviter et qui m'a conduit à écrire ce post.

Ce casse-tête n'est pas juste lié à Lua, j'aurais pu tomber dessus à n'importe quelle occasion. C'est juste que mes expérimentations avec Lua/C++ m'y ont amené plus tôt que prévu.

À votre avis, y a-t-il une solution élégante à ce casse-tête?

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.