Question

Je cherche les règles concernant le passage des fonctions C ++ templates comme arguments.

Ceci est soutenu par C ++ comme le montre un exemple:

#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. recherche sur Google pour « fonction comme argument de modèle » ne conduit pas trop. Et le classique C ++ Modèles Le guide complet ne surprenant pas aussi discuter (à moins pas de ma recherche).

Les questions que j'ai sont si cela est valide C ++ (ou tout simplement une extension largement pris en charge).

Aussi, est-il un moyen de permettre à un foncteur avec la même signature à utiliser de façon interchangeable avec des fonctions explicites lors de ce type d'invocation de modèle?

Ce qui suit fonctionne pas dans le programme ci-dessus, au moins dans Visual C ++ , car la syntaxe est évidemment faux. Ce serait agréable de pouvoir passer une fonction pour un foncteur et vice versa, semblable à la façon dont vous pouvez passer un pointeur de fonction ou foncteur à l'algorithme std :: sort si vous souhaitez définir une opération de comparaison personnalisée.

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

    doOperation<add3>();

Pointeurs à un lien Web ou deux, ou une page dans le livre Modèles C ++ serait apprécié!

Était-ce utile?

La solution

Oui, il est valide.

En ce qui concerne le faire fonctionner avec foncteurs ainsi, la solution habituelle est quelque chose comme ceci:

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

qui peut maintenant être appelé soit:

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

Le problème est que si elle rend difficile pour le compilateur inline l'appel à add2, puisque tout le compilateur sait est qu'un type de pointeur de fonction void (*)(int &) est passé à doOperation. (Mais add3, étant un foncteur, peut être inline 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 pas seulement un pointeur de fonction inconnue.)

Autres conseils

paramètres du modèle peuvent être soit paramétrée par type (typename T) ou valeur (int X).

Le chemin de Templating un morceau de code « traditionnel » C ++ est d'utiliser un foncteur -. À savoir, le code est dans un objet, et l'objet donne ainsi le code type unique

Lorsque vous travaillez avec des fonctions traditionnelles, cette technique ne fonctionne pas bien, parce qu'un changement de type n'indique pas spécifique fonction - plutôt spécifie que la signature de nombreuses fonctions possibles. Donc:

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

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

ne correspond pas à l'affaire foncteur. Dans cet exemple, do_op est instancié pour tous les pointeurs de fonction dont la signature est int X (int, int). Le compilateur devrait être assez agressif inline pleinement ce cas. (Je ne l'exclure cependant, que l'optimisation du compilateur a obtenu assez avancé.)

Une façon de dire que ce code ne fait pas tout à fait ce que nous voulons est:

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

est toujours légal, et bien cela ne s'inline. Pour obtenir inline complet, nous avons besoin de modèle en valeur, de sorte que 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, int b) { return a + b; }
...
int c = do_op<add>(4,5);

Dans ce cas, chaque version instanciée de do_op est instancié avec une fonction spécifique déjà disponible. Nous nous attendons donc le code pour do_op de regarder un peu comme « retour a + b ». (Programmeurs Lisp, arrêtez votre smirking!)

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

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

ne parviendra pas à compiler. GCC dit: « erreur: « func_ptr » ne peut pas apparaître dans une expression constante En d'autres termes, je ne peux pas développer complètement do_op parce que vous me l'avez pas donné assez d'informations au moment du compilateur de savoir ce que notre op est

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

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 fonctionne! (Je ne veux pas dire qu'il est bon C ++, mais ...) Ce qui est arrivé est do_op a été basé sur un modèle autour des signatures des différentes fonctions, et chaque instanciation distinct écrire un code différent de contrainte de type. Ainsi, le code instancié 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, notre cas par valeur exige une correspondance exacte sur les arguments de la fonction.

pointeurs de fonction peuvent être transmises en tant que 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 fonctions plutôt que pointer à la fonction. Au modèle instanciation on passe l'adresse de la fonction plutôt que 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 voulez passer un type foncteur comme 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 passent une instance foncteur 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 à cette apparence uniforme avec un argument de modèle est de définir do_op twice- une fois avec un paramètre non-type 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, je vraiment devrait-il pas à compiler, mais cela a fonctionné pour moi avec gcc-4.8 et Visual Studio 2013.

Dans votre modèle

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

Le paramètre T est un paramètre de modèle non-types. Cela signifie que le comportement de la fonction de modèle change avec la valeur du paramètre (qui doit être fixée lors de la compilation, qui constantes de pointeur de fonction sont).

Si vous voulez somthing qui fonctionne avec les deux objets fonction et des paramètres de fonction dont vous avez besoin d'un modèle typé. Lorsque vous faites cela, cependant, vous devez également fournir une instance d'objet (soit instance d'objet de fonction ou 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 des considérations de performance mineures. Cette nouvelle version peut être moins efficace avec des arguments pointeur de la fonction que le pointeur de fonction particulière est seulement derefenced et appelé lors de l'exécution alors que votre modèle de pointeur de fonction peut être optimisé (peut-être l'appel de fonction inline) en fonction du pointeur de fonction particulière utilisée. objets de fonction peuvent souvent être très efficacement développés avec le modèle typé, bien que le operator() particulier est complètement déterminée par le type de l'objet de la fonction.

La raison pour laquelle votre foncteur exemple ne fonctionne pas est que vous avez besoin d'une instance d'invoquer la operator().

Edit: Passing l'opérateur en tant que travail ne marche pas de référence. Pour simplifier, comprendre comme un pointeur de fonction. Vous envoyez simplement le pointeur, pas une référence. Je pense que vous essayez d'écrire quelque chose comme ça.

struct Square
{
    double operator()(double number) { return number * number; }
};

template <class Function>
double integrate(Function f, double a, double b, unsigned int intervals)
{
    double delta = (b - a) / intervals, sum = 0.0;

    while(a < b)
    {
        sum += f(a) * delta;
        a += delta;
    }

    return sum;
}

. .

std::cout << "interval : " << i << tab << tab << "intgeration = "
 << integrate(Square(), 0.0, 1.0, 10) << std::endl;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top