Une raison de ne pas utiliser les lambdas globales?

89

Nous avions une fonction qui utilisait une lambda interne non capturante, par exemple:

void foo() {
  auto bar = [](int a, int b){ return a + b; }

  // code using bar(x,y) a bunch of times
}

Maintenant, la fonctionnalité implémentée par le lambda est devenue nécessaire ailleurs, donc je vais sortir le lambda de foo()la portée globale / namespace. Je peux soit le laisser comme lambda, ce qui en fait une option copier-coller, soit le changer pour une fonction appropriée:

auto bar = [](int a, int b){ return a + b; } // option 1
int bar(int a, int b){ return a + b; } // option 2

void foo() {
  // code using bar(x,y) a bunch of times
}

Le changer pour une fonction appropriée est trivial, mais cela m'a fait me demander s'il y avait une raison de ne pas le laisser comme lambda? Y a-t-il une raison de ne pas utiliser les lambdas partout au lieu des fonctions globales "normales"?

Baruch
la source
Je suppose que la capture de variables non intentionnelles entraînerait de nombreuses erreurs si les lambdas ne sont pas soigneusement utilisées
macroland
quelques arguments pour ce youtube.com/watch?v=Ft3zFk7VPrE
sudo rm -rf slash

Réponses:

61

Il y a une raison très importante de ne pas utiliser de lambdas globaux: parce que ce n'est pas normal.

La syntaxe de fonction régulière de C ++ existe depuis l'époque de C. Les programmeurs savent depuis des décennies ce que signifie cette syntaxe et comment ils fonctionnent (bien qu'il soit vrai que cette décomposition de fonction en pointeur mord parfois même les programmeurs chevronnés). Si un programmeur C ++ de n'importe quel niveau de compétence au-delà du "débutant absolu" voit une définition de fonction, il sait ce qu'il obtient.

Un lambda global est une bête complètement différente. Il a un comportement différent d'une fonction régulière. Les lambdas sont des objets, contrairement aux fonctions. Ils ont un type, mais ce type est distinct du type de leur fonction. Et ainsi de suite.

Alors maintenant, vous avez élevé la barre en communiquant avec d'autres programmeurs. Un programmeur C ++ doit comprendre les lambdas s'il veut comprendre ce que fait cette fonction. Et oui, nous sommes en 2019, donc un programmeur C ++ décent devrait avoir une idée de ce à quoi ressemble un lambda. Mais c'est toujours une barre plus élevée.

Et même s'ils le comprennent, la question dans l'esprit de ce programmeur sera ... pourquoi l'auteur de ce code l'a-t-il écrit de cette façon? Et si vous n'avez pas une bonne réponse à cette question (par exemple, parce que vous voulez explicitement interdire la surcharge, comme dans les points de personnalisation des plages), alors vous devez utiliser le mécanisme commun.

Préférez les solutions attendues aux nouvelles, le cas échéant. Utilisez la méthode la moins compliquée pour faire passer votre message.

Nicol Bolas
la source
9
"depuis l'époque de C" Bien avant mon ami
Courses de légèreté en orbite
4
@LightnessRaces: Mon point principal était d'exprimer que la syntaxe des fonctions de C ++ existe depuis C, donc beaucoup de gens savent ce que c'est par inspection.
Nicol Bolas
2
Je suis d'accord avec toutes ces réponses, à l'exception de la première phrase. "Ce n'est pas normal" est une déclaration vague et nébuleuse. Peut-être le vouliez-vous dans le sens de "c'est rare et déroutant"?
einpoklum
1
@einpoklum: C ++ a beaucoup de choses qui pourraient être considérées comme "inhabituelles et déroutantes" qui sont encore assez "normales" dans leur domaine (voir la métaprogrammation approfondie des modèles). Je pense que "normal" est parfaitement valable dans ce cas; ce n'est pas une chose qui est dans les "normes" de l'expérience de programmation C ++, à la fois historiquement et aujourd'hui.
Nicol Bolas
@NicolBolas: Mais dans ce sens, c'est un raisonnement circulaire: OP se demande si nous devrions adopter une pratique rare. Les pratiques rares ne sont pas "la norme", presque par définition. Mais nm.
einpoklum
53

Je peux penser à quelques raisons pour lesquelles vous voudriez éviter les lambdas globaux comme remplacements directs pour les fonctions régulières:

  • les fonctions régulières peuvent être surchargées; les lambdas ne le peuvent pas (il existe cependant des techniques pour le simuler)
  • Malgré le fait qu'elles soient fonctionnelles, même un lambda non capturant comme celui-ci occupera de la mémoire (généralement 1 octet pour la non capture).
    • comme souligné dans les commentaires, les compilateurs modernes optimiseront ce stockage loin sous la règle comme si

"Pourquoi ne devrais-je pas utiliser des lambdas pour remplacer des foncteurs avec état (classes)?"

  • les classes ont simplement moins de restrictions que les lambdas et devraient donc être la première chose que vous atteignez pour
    • (données publiques / privées, surcharge, méthodes d'assistance, etc.)
  • si le lambda a un état, il est d'autant plus difficile de raisonner quand il devient global.
    • Nous devrions préférer créer une instance d'une classe à la portée la plus étroite possible
  • il est déjà difficile de convertir un lambda non capturant en un pointeur de fonction, et il est impossible pour un lambda qui spécifie quoi que ce soit dans sa capture.
    • les classes nous donnent un moyen simple de créer des pointeurs de fonction, et ils sont aussi ce que de nombreux programmeurs sont plus à l'aise avec
  • Les lambdas avec n'importe quelle capture ne peuvent pas être construits par défaut (en C ++ 20. Auparavant, il n'y avait aucun constructeur par défaut)
AndyG
la source
Il y a aussi le pointeur "this" implicite vers un lambda (même non capturant) qui se traduit par un paramètre supplémentaire lors de l'appel du lambda vs l'appel de la fonction.
1201ProgramAlarm
@ 1201ProgramAlarm: C'est un bon point; il peut être beaucoup plus difficile d'obtenir un pointeur de fonction pour un lambda (bien que les lambdas non capturants puissent se désintégrer en pointeurs de fonction réguliers)
AndyG
3
D'un autre côté, si elle est passée quelque part au lieu d'être appelée directement, avoir un lambda au lieu d'une fonction favorise l'incrustation. Naturellement, on pourrait emballer le pointeur de fonction dans un std::integral_constantpour cela ...
Deduplicator
@Deduplicator: " avoir un lambda au lieu d'une fonction favorise l'inline " Juste pour être clair, cela ne s'applique que si "quelque part" est un modèle qui prend la fonction qu'il appelle comme un type arbitraire appelable, plutôt qu'un pointeur de fonction.
Nicol Bolas
2
@AndyG Vous oubliez la règle comme si: les programmes qui ne demandent pas la taille du lambda (parce que… pourquoi?!), Ni ne créent de pointeur vers celui-ci, n'ont pas besoin de le faire (et généralement pas) lui allouer de l'espace.
Konrad Rudolph
9

Après avoir demandé, j'ai pensé à une raison pour ne pas le faire: comme il s'agit de variables, elles sont sujettes au fiasco de l'ordre d'initialisation statique ( https://isocpp.org/wiki/faq/ctors#static-init-order ), ce qui pourrait provoquer des bugs sur toute la ligne.

Baruch
la source
Vous pourriez en faire des constexprlambdas ... le vrai point est: utilisez simplement une fonction.
einpoklum
8

Y a-t-il une raison de ne pas utiliser les lambdas partout au lieu des fonctions globales "normales"?

Un problème d'un certain niveau de complexité nécessite une solution au moins de la même complexité. Mais s'il existe une solution moins complexe pour le même problème, alors il n'y a vraiment aucune justification pour utiliser le plus complexe. Pourquoi introduire une complexité dont vous n'avez pas besoin?

Entre un lambda et une fonction, une fonction est simplement le type d'entité le moins complexe des deux. Vous n'avez pas à justifier de ne pas utiliser de lambda. Vous devez justifier d'en utiliser un. Une expression lambda introduit un type de fermeture, qui est un type de classe sans nom avec toutes les fonctions membres spéciales habituelles, un opérateur d'appel de fonction et, dans ce cas, un opérateur de conversion implicite vers le pointeur de fonction, et crée un objet de ce type. L'initialisation de la copie d'une variable globale à partir d'une expression lambda fait bien plus que simplement définir une fonction. Il définit un type de classe avec six fonctions déclarées implicitement, définit deux autres fonctions d'opérateur et crée un objet. Le compilateur doit faire beaucoup plus. Si vous n'avez besoin d'aucune des fonctionnalités d'un lambda, alors n'utilisez pas de lambda…

Michael Kenzel
la source
6

Les lambdas sont des fonctions anonymes .

Si vous utilisez un lambda nommé, cela signifie que vous utilisez essentiellement une fonction anonyme nommée. Pour éviter cet oxymore, vous pourriez aussi bien utiliser une fonction.

Eric Duminil
la source
1
Cela semble être un argument de la terminologie, à savoir. confusion de la dénomination et du sens. Il peut facilement être réfuté en définissant qu'un lambda nommé n'est plus anonyme (duh). Le problème ici n'est pas de savoir comment le lambda est utilisé, c'est que «fonction anonyme» est un terme impropre et n'est plus précis lorsqu'un lambda est lié à un nom.
Konrad Rudolph
@KonradRudolph: Ce n'est pas seulement de la terminologie, c'est pourquoi ils ont été créés en premier lieu. Au moins historiquement, les lambdas sont des fonctions anonymes, et c'est pourquoi il est difficile de les nommer, en particulier lorsque des fonctions sont attendues à la place.
Eric Duminil
1
Non. Les lambdas sont régulièrement nommés (juste généralement localement), il n'y a rien de déroutant à ce sujet.
Konrad Rudolph
1
En désaccord avec les fonctions anonymes , il s'agit plus d'un foncteur, mais de toute façon, je ne vois pas le problème d'avoir une instance (nommée) au lieu de forcer à les utiliser uniquement comme référence de valeur. (car votre point devrait également s'appliquer à la portée locale).
Jarod42
5

s'il y a une raison de ne pas le laisser comme lambda? Y a-t-il une raison de ne pas utiliser les lambdas partout au lieu des fonctions globales "normales"?

Nous avions l'habitude d'utiliser des fonctions au lieu de foncteur global, donc cela casse la cohérence et le principe du moindre étonnement .

Les principales différences sont:

  • les fonctions peuvent être surchargées, contrairement aux foncteurs.
  • les fonctions peuvent être trouvées avec ADL, pas avec les foncteurs.
Jarod42
la source