279 votes

fonction passée comme argument template

Je suis à la recherche pour les règles impliquant passant modèles C++ des fonctions comme arguments. C'est pris en charge par le C++, comme illustré par un exemple ici:

#include <iostream>

void add1(int &v)
{
  v+=1;
}

void add2(int &v)
{
  v+=2;
}

template <void (*T)(int &)>
void doOperation()
{
  int temp=0;
  T(temp);
  std::cout << "Result is " << temp << std::endl;
}

int main()
{
  doOperation<add1>();
  doOperation<add2>();
}

L'apprentissage de cette technique est difficile, cependant. Googler pour "fonctionner comme un modèle, l'argument de ne pas mener à bien. Et le classique des Modèles C++ Le Guide Complet étonnamment aussi ne discutent pas (au moins pas de ma recherche).

Les questions que j'ai est de savoir si c'est valable en C++ (ou juste une partie largement pris en charge extension).

Aussi, est-il un moyen pour permettre à un foncteur avec la même signature pour être utilisé de façon interchangeable avec des fonctions explicites lors de ce type de modèle invocation? Ce qui suit ne pas travailler dans le programme ci-dessus , au moins dans Visual C, car la syntaxe est évidemment faux. Il serait agréable d'être en mesure de passer d'une fonction pour un foncteur, et vice-versa, semblable à la façon dont vous pouvez passer un pointeur de fonction ou de functor pour le std::algorithme de tri si vous souhaitez définir une comparaison personnalisée de l'opération.

   struct add3 {
      void operator() (int &v) {v+=3;}
    };
...

    doOperation<add3>();

Des pointeurs vers un lien web ou deux, ou une page dans les Modèles C++ livre serait appréciée!

162voto

jalf Points 142628

Oui, il est valide.

Comme pour le faire fonctionner avec les foncteurs ainsi, la solution habituelle est quelque chose comme ceci à la place:

template <typename F>
void doOperation(F f)
{
  int temp=0;
  f(temp);
  std::cout << "Result is " << temp << std::endl;
}

qui peut maintenant être appelé comme:

doOperation(add2);
doOperation(add3());

Le problème avec cela est que si cela rend difficile pour le compilateur associé à l'appel d' add2, puisque tout le compilateur sait, c'est que d'un pointeur de fonction de type void (*)(int &) de doOperation. (Mais add3, être un foncteur, peut être incorporé facilement. Ici, le compilateur sait qu'un objet de type add3 est passé à la fonction, ce qui signifie que la fonction à appeler est - add3::operator(), et non pas seulement de certains inconnus pointeur de fonction.)

84voto

Ben Supnik Points 421

Les paramètres de modèle peut être paramétrée par le type (typename T) ou en valeur (int X).

Le "traditionnel" C++ façon de templating un morceau de code est d'utiliser un foncteur - qui est, le code est dans un objet, et l'objet donne donc le code de type unique.

Lorsque l'on travaille avec des fonctions traditionnelles, cette technique ne fonctionne pas bien, parce qu'un changement dans le type n'indique pas une spécifique à la fonction - c'est plutôt elle indique que la signature de nombreuses fonctions. Donc:

template<typename OP>
int do_op(int a, int b, OP op)
{
  return op(a,b,);
}
int add(int a, b) { return a + b; }
...

int c = do_op(4,5,add);

N'est-ce pas l'équivalent du foncteur cas. Dans cet exemple, do_op est instancié pour tous les pointeurs de fonction dont la signature est de type int X (int, int). Le compilateur devrait être assez agressif pour pleinement inline ce cas. (Je ne l'exclut pas cependant, comme l'optimisation du compilateur a reçu assez avancé.)

Une façon de dire que ce code n'est pas tout à fait faire ce que nous voulons, c'est:

int (* func_ptr)(int, int) = add;
int c = do_op(4,5,func_ptr);

est toujours légale, et clairement, ce n'est pas insérée. Pour obtenir l'in-lining, nous avons besoin d'un modèle par valeur, donc la fonction est entièrement disponible dans le modèle.

typedef int(*binary_int_op)(int, int); // signature for all valid template params
template<binary_int_op op>
int do_op(int a, int b)
{
 return op(a,b);
}
int add(int a, b) { return a + b; }
...
int c = do_op<add>(4,5);

Dans ce cas, chaque instancié version de do_op est instancié avec une fonction spécifique déjà disponibles. Ainsi, nous attendons le code pour do_op de regarder un peu comme "return a + b". (Programmeurs Lisp, arrêtez votre sourire!)

Nous pouvons également confirmer que cela est plus proche de ce que nous voulons parce que c':

int (* func_ptr)(int,int) = add;
int c = do_op<func_ptr>(4,5);

échoue à compiler. GCC dit: "erreur:" func_ptr " ne peut pas apparaître dans une constante de l'expression. En d'autres termes, je ne peux pas entièrement développez do_op parce que vous n'avez pas donné assez d'infos au compilateur de temps pour savoir ce que notre op.

Donc, si le deuxième exemple est vraiment bien inline notre op, et la première n'est pas, à quoi bon le modèle? Qu'est-ce que ça fait? La réponse est: type de contrainte. Ce riff sur le premier exemple:

template<typename OP>
int do_op(int a, int b, OP op) { return op(a,b); }
float fadd(float a, float b) { return a+b; }
...
int c = do_op(4,5,fadd);

Cet exemple va travailler! (Je ne dis pas que c'est bon, C++, mais...) Ce qui s'est passé est do_op a été basé sur un modèle autour de la signatures des fonctions différentes, et chaque instanciation écrira différents de contrainte de type code. Ainsi, le instancié code pour do_op avec fadd ressemble à quelque chose comme:

convert a and b from int to float.
call the function ptr op with float a and float b.
convert the result back to int and return it.

Par comparaison, nos en-cas de valeur nécessite une correspondance exacte avec les arguments de la fonction.

29voto

Kietz Points 352

Pointeurs de fonction peuvent être passés comme paramètres de modèle, et cela fait partie de la norme C++ . Cependant, dans le modèle, ils sont déclarés et utilisés comme des fonctions plutôt que de pointeur de fonction. Au modèle d'instanciation on passe l'adresse de la fonction plutôt que de simplement le nom.

Par exemple:

int i;


void add1(int& i) { i += 1; }

template<void op(int&)>
void do_op_fn_ptr_tpl(int& i) { op(i); }

i = 0;
do_op_fn_ptr_tpl<&add1>(i);

Si vous souhaitez passer un foncteur type comme un argument de modèle:

struct add2_t {
  void operator()(int& i) { i += 2; }
};

template<typename op>
void do_op_fntr_tpl(int& i) {
  op o;
  o(i);
}

i = 0;
do_op_fntr_tpl<add2_t>(i);

Plusieurs réponses passer un foncteur exemple comme argument:

template<typename op>
void do_op_fntr_arg(int& i, op o) { o(i); }

i = 0;
add2_t add2;

// This has the advantage of looking identical whether 
// you pass a functor or a free function:
do_op_fntr_arg(i, add1);
do_op_fntr_arg(i, add2);

Le plus proche que vous pouvez obtenir à cet aspect uniforme avec un argument de modèle est de définir do_op deux fois, une fois avec un non-type de paramètre et une fois avec un paramètre de type.

// non-type (function pointer) template parameter
template<void op(int&)>
void do_op(int& i) { op(i); }

// type (functor class) template parameter
template<typename op>
void do_op(int& i) {
  op o; 
  o(i); 
}

i = 0;
do_op<&add1>(i); // still need address-of operator in the function pointer case.
do_op<add2_t>(i);

Honnêtement, j'ai vraiment attendu ce pas à compiler, mais cela a fonctionné pour moi avec gcc-4.8 et Visual Studio 2013.

11voto

Charles Bailey Points 244082

Dans votre modèle

template <void (*T)(int &)>
void doOperation()

Le paramètre T est un non-type de paramètre du modèle. Cela signifie que le comportement de la fonction de modèle changements avec la valeur du paramètre (qui doit être fixée au moment de la compilation, ce qui le pointeur de la fonction des constantes).

Si vous voulez quelque chose qui fonctionne à la fois en fonction des objets et des paramètres de la fonction vous avez besoin d'un tapé modèle. Lorsque vous faites cela, cependant, vous devez également fournir une instance de l'objet (soit la fonction de l'instance d'un objet ou d'un pointeur de fonction) à la fonction au moment de l'exécution.

template <class T>
void doOperation(T t)
{
  int temp=0;
  t(temp);
  std::cout << "Result is " << temp << std::endl;
}

Il y a quelques petites considérations de performance. Cette nouvelle version peut être moins efficace avec le pointeur de fonction des arguments que la fonction de pointeur n'est derefenced et appelé au moment de l'exécution alors que votre pointeur de fonction template peut être optimisé (éventuellement l'appel de la fonction inline) en fonction de pointeur de fonction utilisé. La fonction des objets peuvent souvent être très efficacement élargi avec l'tapé modèle, même si, comme le particulier operator() est complètement déterminé par le type de la fonction de l'objet.

3voto

Nikolai N Fetissov Points 52093

La raison pour laquelle votre exemple functor ne fonctionne pas, c’est que vous avez besoin d’une instance pour appeler le `` .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X