Fonction to Lambda

En C ++, il est souvent utile de travailler avec des objects lambdas et fonction plutôt qu’avec des pointeurs de fonction. La raison en est qu’un type d’object de fonction ou de fonction lambda code intégralement ce qui est appelé (et permet donc l’inline), alors que le type d’un pointeur de fonction code simplement la signature. J’ai eu la pensée intéressante qu’il pourrait être utile d’utiliser du code générique pour créer un lambda qui appelle une fonction donnée. En d’autres termes, une fonction d’ordre supérieur qui, à partir d’une fonction spécifiée, renvoie un lambda qui l’appelle. Pour éviter toute indirection, les parameters de modèle de type non pointeur de fonction doivent être utilisés. Le problème est que pour spécifier un tel paramètre de modèle non typé, vous devez connaître la signature de la fonction. Je ne parviens pas à contourner la fonction en question deux fois: une fois pour en déduire les types et une fois pour se spécialiser dans la fonction. Ma meilleure tentative ressemble à ceci:

template  struct makeLambdaHelper; template  struct makeLambdaHelper { template  static auto blah() { return [] (Args && ... args) { return F(std::forward(args)...); }; } }; 

Pour l’utiliser, vous feriez:

 void f(int, int) { std::cerr << "f\n"; }; ... auto lam = makeLambdaHelper::blah(); lam(0,0); 

Lorsque le type de f est passé en tant qu’argument de modèle à makeLambdaHelper, sa signature est d’abord déduite. Ensuite, la structure a une fonction statique qui déclare un paramètre de modèle non-type; alors nous passons f lui-même et sortons un lambda. Devoir passer deux fois comme ça est assez moche; Est-il possible de faire mieux, peut-être en faisant quelque chose d’intelligent avec les parameters de modèle par défaut?

Si vous voulez juste envelopper un pointeur de fonction dans un object fonction, je recommanderais une macro:

 #define WRAP_FN(f) [](auto&&... args) -> decltype(auto) { \ return f(std::forward(args)...); }; 

A utiliser comme:

 auto lam = WRAP_FN(foo); 

L’avantage de la macro est qu’elle gérera en outre les noms surchargés , ce qui vous permettra de transférer des fonctions surchargées dans des algorithmes std et de les laisser faire ce que vous voulez.

Étant donné l’utilisation de votre code à titre d’exemple,

 makeLambdaHelper::blah(); 

Vous déclarez une classe, avec la signature de la fonction f (en utilisant un decltype ), puis vous référencez à nouveau la fonction f dans l’appel de la fonction blah – vous référencez f deux fois, une fois pour la valeur (le pointeur) et une fois pour le type (la signature). Je pense que vous pourriez minimiser la syntaxe ici et les combiner (en utilisant la déduction de type de modèle).

 void f(int, int) { std::cout << "f\n"; } template  auto make_lambda(R(*F)(Args...)) { return [F] (Args && ... args) {// F pointer captured by value return F(std::forward(args)...); }; } int main() { auto lam = make_lambda(&f); lam(0,0); } 

Code de démonstration .

La fonction make_lambda prend le pointeur sur la fonction f une fois et utilise la déduction de modèle pour obtenir la signature requirejse.


Comme indiqué dans les commentaires, une partie raisonnable de la question est la question de ne pas avoir d’indirection (y compris l’appel final) dans le code généré; l’indirection dépend de nombreux facteurs, dont le compilateur utilisé, le niveau d’optimisation appliqué et la capacité en ligne de la fonction f pour commencer. Voir ces exemples pour une modification du compilateur , une modification de la complexité de la fonction et une modification de la “capacité en ligne” de f ; tous produisent des résultats variables d’indirection, d’appels en queue et de code en ligne. Comme vous l’avez noté dans les commentaires, il est important que f puisse être (et est) aligné (cela revient souvent aux optimisations des compilateurs) pour commencer; sinon, il y a peu de différence entre toutes les options.

En ce qui concerne la syntaxe, hormis la macro, il semble que peu de choses puissent être faites avec des modèles de classe lorsque les arguments de modèle nécessitent à la fois le type et une valeur (le pointeur ici); Les modèles de fonction peuvent déduire le type en fonction de la valeur. Gardez à l’esprit que si la performance de l’indirection est mesurable et significative dans l’application, vous devrez peut-être vous en sortir et utiliser la forme la plus longue de la syntaxe, sinon privilégiez une syntaxe plus facile à gérer, plus courte et plus évidente (n’importe lequel d’entre eux). .


Compte tenu de la syntaxe originale, il pourrait être un peu nettoyé; cela n’améliore en rien les problèmes fondamentaux, mais seulement le fait de placer le type et la valeur “proches” dans la syntaxe du modèle faciliterait la maintenance du code (vos résultats peuvent bien sûr varier);

 template  struct lambda_maker; template  struct lambda_maker { static auto make() { return [] (Args && ... args) { return F(std::forward(args)...); }; } }; // ... auto lam = lambda_maker::make(); lam(2, 3);