Je cherche les règles impliquant le passage des fonctions de modèles C ++ comme arguments.
Ceci est pris en charge par C ++ comme le montre 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>();
}
Il est cependant difficile d'apprendre à connaître cette technique. La recherche de "fonction comme argument de modèle" ne mène pas à grand-chose. Et les modèles C ++ classiques Le guide complet ne l'étonnent pas non plus (du moins pas de ma recherche).
Les questions que je me pose sont de savoir s'il s'agit d'un C ++ valide (ou simplement d'une extension largement prise en charge).
De plus, existe-t-il un moyen de permettre à un foncteur avec la même signature d'être utilisé de manière interchangeable avec des fonctions explicites pendant ce type d'invocation de modèle?
Ce qui suit ne fonctionne pas dans le programme ci-dessus, au moins dans Visual C ++ , car la syntaxe est évidemment incorrecte. Ce serait bien de pouvoir désactiver une fonction pour un foncteur et vice versa, de la même manière que vous pouvez passer un pointeur de fonction ou un 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>();
Des pointeurs vers un ou deux liens Web ou une page du livre des modèles C ++ seraient appréciés!
la source
-std=gnu++17
. Puis-je utiliser le résultat d'un opérateur de conversion lambda constexpr sans capture C ++ 17 comme argument de non-type de modèle de pointeur de fonction? .Réponses:
Oui, c'est valable.
Quant à le faire fonctionner avec des foncteurs également, la solution habituelle est quelque chose comme ceci à la place:
qui peut maintenant être appelé soit:
Voir en direct
Le problème avec cela est que si cela rend difficile pour le compilateur d'appeler l'appel
add2
, car tout ce que le compilateur sait, c'est qu'un type de pointeur de fonctionvoid (*)(int &)
est passé àdoOperation
. (Maisadd3
, étant un foncteur, il peut être facilement inséré. Ici, le compilateur sait qu'un objet de typeadd3
est passé à la fonction, ce qui signifie que la fonction à appeler estadd3::operator()
, et pas seulement un pointeur de fonction inconnu.)la source
template <typename F> void doOperation(F&& f) {/**/}
), donc bind par exemple peut passer une expression bind au lieu de la lier?Les paramètres du modèle peuvent être paramétrés par type (nom de type T) ou par valeur (int X).
La manière «traditionnelle» C ++ de modéliser un morceau de code consiste à utiliser un foncteur - c'est-à-dire que le code se trouve dans un objet et que l'objet donne ainsi au code un type unique.
Lorsque vous travaillez avec des fonctions traditionnelles, cette technique ne fonctionne pas bien, car un changement de type n'indique pas une fonction spécifique - elle spécifie plutôt uniquement la signature de nombreuses fonctions possibles. Alors:
N'est pas équivalent au cas du 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 pour aligner entièrement ce cas. (Je ne l'exclurais pas cependant, car l'optimisation du compilateur est devenue assez avancée.)
Une façon de dire que ce code ne fait pas tout à fait ce que nous voulons est:
est toujours légal, et il est clair que cela ne s'aligne pas. Pour obtenir une intégration complète, nous devons créer un modèle par valeur, de sorte que la fonction est entièrement disponible dans le modèle.
Dans ce cas, chaque version instanciée de do_op est instanciée avec une fonction spécifique déjà disponible. Ainsi, nous nous attendons à ce que le code de do_op ressemble beaucoup à "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:
échouera à la compilation. 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 ne m'avez pas donné suffisamment d'informations au moment du compilateur pour savoir ce qu'est notre op.
Donc, si le deuxième exemple est vraiment en train d'intégrer pleinement notre op, et le premier ne l'est pas, à quoi sert le modèle? Qu'est-ce que ça fait? La réponse est: saisissez la contrainte. Ce riff sur le premier exemple fonctionnera:
Cet exemple fonctionnera! (Je ne dis pas que c'est du bon C ++ mais ...) Ce qui s'est passé, c'est que do_op a été calqué sur les signatures des différentes fonctions, et chaque instanciation séparée écrira un code de coercition de type différent. Ainsi, le code instancié pour do_op avec fadd ressemble à quelque chose comme:
Par comparaison, notre cas par valeur nécessite une correspondance exacte sur les arguments de la fonction.
la source
int c = do_op(4,5,func_ptr);
"n'est clairement pas alignée".Les pointeurs de fonction peuvent être passés en tant que paramètres de modèle, et cela fait partie du C ++ standard . Cependant, dans le modèle, ils sont déclarés et utilisés en tant que fonctions plutôt que pointeur vers fonction. À l' instanciation du modèle on passe l'adresse de la fonction plutôt que juste le nom.
Par exemple:
Si vous souhaitez passer un type de foncteur comme argument de modèle:
Plusieurs réponses passent une instance de foncteur en argument:
Le plus proche de cette apparence uniforme avec un argument de modèle est de définir
do_op
deux fois - une fois avec un paramètre non type et une fois avec un paramètre type.Honnêtement, je m'attendais vraiment à ce que cela ne compile pas, mais cela a fonctionné pour moi avec gcc-4.8 et Visual Studio 2013.
la source
Dans votre modèle
Le paramètre
T
est un paramètre de modèle non type. Cela signifie que le comportement de la fonction de modèle change avec la valeur du paramètre (qui doit être fixée au moment de la compilation, quelles sont les constantes de pointeur de fonction).Si vous voulez quelque chose qui fonctionne avec les objets de fonction et les paramètres de fonction, vous avez besoin d'un modèle typé. Cependant, lorsque vous effectuez cette opération, vous devez également fournir une instance d'objet (soit une instance d'objet de fonction ou un pointeur de fonction) à la fonction au moment de l'exécution.
Il existe quelques considérations de performances mineures. Cette nouvelle version peut être moins efficace avec des arguments de pointeur de fonction car le pointeur de fonction particulier est uniquement déréférencé et appelé au moment de l'exécution tandis que votre modèle de pointeur de fonction peut être optimisé (éventuellement l'appel de fonction en ligne) en fonction du pointeur de fonction particulier utilisé. Les objets fonction peuvent souvent être développés de manière très efficace avec le modèle typé, bien que le particulier
operator()
soit complètement déterminé par le type de l'objet fonction.la source
La raison pour laquelle votre exemple de foncteur ne fonctionne pas est que vous avez besoin d'une instance pour appeler le
operator()
.la source
Edit: Passer l'opérateur comme référence ne fonctionne pas. Pour plus de simplicité, comprenez-le 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.
. .
la source