web-dev-qa-db-fra.com

Comment créer un tableau de pointeurs de fonction de différents prototypes?

J'ai quelques fonctions définies comme ceci:

ParentClass*    fun1();
ParentClass*    fun2();
ParentClass*    fun3(bool inp=false);
ChildClass*     fun4();
ChildClass*     fun5(int a=1, int b=3);

Je voudrais les mettre dans un tableau de quelque sorte comme suit:

void* (*arr[5])() = {
    (void* (*)())fun1,
    (void* (*)())fun2,
    (void* (*)())fun3,
    (void* (*)())fun4,
    (void* (*)())fun5
}

Maintenant, je voudrais utiliser ce tableau de fonctions simplement comme

for(int i=0; i<5; i++)
    someFunction(arr[i]());

Maintenant, je me rends compte ici que le problème est void* (*arr[5])(), mais étant donné que je souhaite uniquement utiliser les fonctions sans fournir d'argument, je souhaiterais que tous ceux-ci fassent partie du même tableau.

Ce sont des manières très C-style de le faire, cependant. Existe-t-il une meilleure façon de le faire en utilisant des modèles en C++?

28
vc669

C-style ou pas, ce que vous avez est un comportement indéfini. Utilisez lambdas:

void (*arr[5])() = {
    [] { fun1(); },
    [] { fun2(); },
    [] { fun3(); },
    [] { fun4(); },
    [] { fun5(); }
};

Celles-ci sont acceptables car elles exécutent l'appel avec le type correct de la fonction et sont elles-mêmes convertibles en void (*)().

Le transfert de la valeur renvoyée reste assez simple, car le lambda fournit un contexte pour la conversion. Dans votre cas, puisque ChildClass hériterait soi-disant de ParentClass, une conversion implicite suffit:

ParentClass *(*arr[5])() = {
    []() -> ParentClass * { return fun1(); },
    []() -> ParentClass * { return fun2(); },
    []() -> ParentClass * { return fun3(); },
    []() -> ParentClass * { return fun4(); },
    []() -> ParentClass * { return fun5(); }
};
42
Quentin

mais étant donné que je veux seulement utiliser les fonctions sans fournir d'argument

Cela ne fonctionne tout simplement pas comme ça. Vous êtes-vous déjà demandé, alors, lorsque vous placez des déclarations de fonction dans un en-tête, pourquoi vous devez écrire des paramètres par défaut dans cet en-tête et ne pouvez pas les placer dans la définition dans le fichier source de l'implémentation?

En effet, les paramètres par défaut ne sont en fait pas "intégrés" dans la fonction, mais utilisés par le compilateur pour augmenter un appel de fonction avec ces paramètres à un emplacement appelant, où ces paramètres sont omis. (EDIT: En outre, comme @Aconcagua l’a si bien observé dans un commentaire, les paramètres par défaut étant généralement définis dans le cadre d’une déclaration de fonction d’en-tête, toute modification des valeurs par défaut nécessite une recompilation complète de toute unité de compilation incluant ces unités. en-têtes, déclarations de fonction ergo, pour que la modification soit effective!)

Bien qu'il soit parfaitement possible de faire une folie de casting de type vraiment étrange pour construire un tableau de pointeurs de fonction comme celui-ci, vous devrez éventuellement rétablir la signature d'appel de fonction d'origine pour ne pas invoquer un comportement indéfini.

Le cas échéant, vous devrez relier le pointeur de la fonction, ainsi qu'un ensemble de paramètres par défaut d'un type quelconque permettant d'abstraire l'appelant, fournit les paramètres et offre à l'extérieur une interface polymorphe. Donc, vous auriez un std::vector<function_binder> Ou function_binder[] Où le classeur de fonction a un operator() qui appelle la fonction.

Mais lorsque vous faites une liaison en premier lieu, vous pouvez la lier dans une fonction anonyme, c’est-à-dire lambdas. Au moment de l'instanciation lambda, les paramètres par défaut sont liés.

std::vector<void(*)()> fvec = {
    []{ func0(); },
    []{ func1(); },
    []{ func2(); },
}
16
datenwolf

Vous pouvez utiliser std::bind

std::function<ParentClass *(void)> arr[5] = {
    std::bind(&fun1),
    std::bind(&fun2),
    std::bind(&fun3, false),
    std::bind(&fun4),
    std::bind(&fun5, 1, 3)
};

maintenant tu peux faire

for(int i=0; i<5; i++)
    arr[i]();

Vous devez vous assurer que tous les paramètres de fonction de toutes les fonctions sont liés.

Cela fonctionne également bien avec les fonctions membres. Il vous suffit de lier la référence à l'objet (par exemple, this) en tant que premier paramètre.

9
Detonar

A c ++ 2 solution:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }


template<auto f, class R, class...Args>
struct explicit_function_caster {
  using Sig=R(Args...);
  using pSig=Sig*;
  constexpr operator pSig()const {
    return [](Args...args)->R {
      return static_cast<R>(f(std::forward<Args>(args)...));
    };
  }
};

template<auto f>
struct overload_storer_t {
  template<class R, class...Args>
  constexpr (*operator R() const)(Args...) const {
    return explicit_function_caster<f, R, Args...>{};
  }
  template<class...Args>
  auto operator()(Args&&...args)
  RETURNS( f( std::forward<Args>(args)... ) )
};
template<auto f>
overload_storer_t<f> generate_overloads={};

#define OVERLOADS_OF(...) \
  generate_overloads< \
    [](auto&&...args) \
    RETURNS( __VA_ARGS__( decltype(args)(args)... ) ) \
  >

ce qui fait beaucoup de casse-tête, mais nous obtient:

ParentClass* (*arr[5])() = {
  OVERLOADS_OF(fun1),
  OVERLOADS_OF(fun2),
  OVERLOADS_OF(fun3),
  OVERLOADS_OF(fun4),
  OVERLOADS_OF(fun5)
};
void (*arr2[5])() = {
  OVERLOADS_OF(fun1),
  OVERLOADS_OF(fun2),
  OVERLOADS_OF(fun3),
  OVERLOADS_OF(fun4),
  OVERLOADS_OF(fun5)
};

fondamentalement generate_overloads<x> prend un objet appelable constexprx et vous permet de le convertir, au moment de la compilation, en pointeur sur une fonction de toute signature compatible et de l'appeler avec (presque) n'importe quelle signature.

Pendant ce temps, OVERLOADS_OF convertit un nom de fonction en un objet constexpr qui surcharge la résolution de ce nom de fonction. Je l'utilise ici parce que fun3 en tant que pointeur de fonction ne connaît pas ses arguments par défaut, mais il le sait au moment de la résolution du problème de surcharge.

Dans ce cas particulier, il est beaucoup plus facile de simplement écrire des lambdas de jouet pour faire ce travail; Il s’agit simplement d’une tentative d’automatisation de l’écriture de ces lambdas jouets pour des signatures compatibles arbitraires.

3

Comme vous avez tagué question avec C++ 14, vous ne devriez pas utiliser les pointeurs de fonction!

Avec C++ 14, vous devriez préférer std::function et lambdas.

Aussi, vous ne devriez pas utiliser de tableau de style C, mais seulement std::array et/ou std::vector.

Évitez également les pointeurs bruts, utilisez std::unique_ptr et std::shared_ptr.

Le moyen le plus simple et le meilleur de le résoudre est:

std::array<std::function<ParentClass*()>,5> arr {
    []() { return fun1(); },
    []() { return fun2(); },
    []() { return fun3(true); },
    []() { return fun4(); },
    []() { return fun5(7, 9); }
};

Pourquoi pas un simple tableau de pointeurs comme dans la réponse @Quentin? Il a utilisé lambdas, mais il ne peut pas utiliser lambda qui lie quoi que ce soit (si vous en avez besoin).

1
Marek R