J'ouvre (encore) un nouveau sujet pour vous poser quelques questions sur ... Les lambdas. En effet le post de @jo_link_noirici m'a assez intrigué et je me dis que, c'est quand même puissant un lambda. Cependant je ne comprends pas tout :
Pourquoi mettre des paramètres dans les crochets alors que on peut aussi en mettre entre parenthèses : par exemple :
int x(5);
[x] (int test) {//contenue};
J'ai lu la documentation des lambdas dans cpp reference et je n'ai pas compris ce que c'était que exception et attribute (ce que ça permet de faire, dans quel cas il faut les utiliser etc...).
Dans quel cas doit-on utiliser un lambda ? Comment peut-on le savoir ?
Les paramètre entre les crochets sont ce qu'on appelle la capture. Ils permettent de définir quelles variables vont être disponibles dans notre lambdas. Les arguments de la lambda c'est autre chose, c'est ce qui sera passé au moment où on exécute la lambda.
Par exemple dans ce code, on capture tout par référence (avec la capture &) et on reçoit un entier en paramètre quand on appelle la lambda.
#include <iostream>
int main()
{
int x = 123;
auto f = [&] (int y) {
std::cout << "x par capture vaut : " << x << std::endl;
std::cout << "y passé en paramètre vaut : " << y << std::endl;
};
f(/* y */ 456);
return 0;
}
Et pour répondre à ta question c'est très simple, on peut prendre en capture que les paramètre que l'on connait au moment où on définit la lambda. En revanche, les arguments passés en paramètre eux peuvent être déterminés plus tard, par exemple des callbacks où on veut appeler une fonction quand quelque chose a changé.
Exemple dans une interface graphique, l'utilisateur saisit un texte et nous souhaitons récupérer ce texte lorsqu'il a été modifié, dans ce cas le texte sera passé en paramètre puisqu'il nous est impossible de le connaître "à l'avance".
input->onTextChanged([] (const std::string &text) {
// nouveau texte ici...
});
- Edité par markand 27 octobre 2015 à 11:06:05
git is great because Linus did it, mercurial is better because he didn't.
Les paramètre entre les crochets sont ce qu'on appelle la capture. Ils permettent de définir quelles variables vont être disponibles dans notre lambdas. Les arguments de la lambda c'est autre chose, c'est ce qui sera passé au moment où on exécute la lambda.
D'accord, mais du coup, si on fait ceci :
[a, &b] {a = 10, b = 12};
Quelle différence fondamentale ça fait si on passe a et b en paramètre ?
Dans la capture, les variables doivent être dans le même scope, en argument, pas obligatoirement, puisqu'on peut l'appeler depuis n'importe où, du moment qu'on a accès à la lambda
Dans ton exemple, le seul intérêt est que assert est aussi une macro et donc il est nécessaire de passer par une macro pour garder le contexte compile-time.
Un exemple d'utilisation cité pourrait être une fonction de callback.
Dans quel cas l'utilisation d'un lambda peut-être utile ?
Pour passer un foncteur à une fonction de la bibli standard (parce que c'est plus simple à faire) ou quand on utilise cette fonction dans un endroit précis de code et que ça vaut pas le coût de créer une fonction classique pour ça, quand tu veux utiliser des variables locales dans la fonction...
Merci @RPGamer pour ces précisions, j'ai quand même une petite dernière question, mais plus sur la notion de callback. Si j'ai bien compris, une notion de callback est d'appeler une fonction qui pourra être rappelé par la suite (par exemple une fonction qui trouve un mot dans une chaîne). J'ai bien compris ?
Les callbacks c'est une pratique érigée au rang de dogme dans certains langages, en particulier les langages pouvant traiter les événements. Ca permet de généraliser un comportement lors de mécanismes asynchrones. Par exemple, je veux que lorsque l'utilisateur presse sur une touche du clavier, soit la touche est affichée à l'écran, soit elle est envoyée à un serveur qui fera quelque chose de son côté, voir autre chose encore. Pour différencier ces 2 comportements, je peux écrire 2 callbacks différentes. Le code qui détecte la touche s'occupera d'appeler la fonction de callback que je lui aurais passé.
Oula encore pas mal de termes assez "abstrait", je vais chercher dans mon coin, mais ce serez très gentil de votre part (et je sais que vous êtes très gentils ) de m'expliquer rapidement :
programmation Asynchrone
programmation Evénementielle
système d'observation.
Pas que j'ai la flemme de chercher (d'ailleur je vais voir de ce pas toute ces notions), mais ça fait pas mal de nouveau concept
D'accord, je comprends un peut mieux merci. Par contre du coup sur ce que vous m'avez dit j'aimerai soulever un point qui est le suivant :
On peut appeler le lambda n'importe où du moment qu'on y a accès.
Et c'est là que je me pose la question suivante :
Un macro serait-elle intéressante dans ce genre de cas ?
Les macros ne sont JAMAIS la solution...(commence par lire ceci, puis suis les liens "evil#1","evil#2", "evil#3" et "evil#4"
Peut-on la mettre dans une classe ? Si oui, par le biais d'un foncteur ?
Cordialement,
Bien sur, on peut utiliser une lambda expression dans n'importe quelle fonction, quelle qu'elle soit...
Note cependant que, par nature, une lambda expression a un scope particulièrement restreint et qu'il n'est réellement intéressant de l'utiliser que si tu as, effectivement, besoin d'une "sous-routine" dans une situation bien particulière, qui ne se retrouve pas "ailleurs dans le code".
Mais, autrement, cela peut s'avérer particulièrement utile partout où tu aurais besoin de recourir soit à un pointeur de fonction (quelle horreur, depuis l'arrivée de std::function) soit à un foncteur (ou plutôt un prédicat sans doute) qui ne serait utilisé qu'à un endroit bien particulier du code
En effet, si tu te rend compte que la logique de ta lambda doit être répétée à différents endroits de ton code, tu as très certainement intérêt à utiliser un foncteur ou une std::function
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
D'accord, @koala01, merci de ces précision et merci pour le lien
Après c'est encore un peut obscur pour moi, mais je pense que ça viendra si un jour je dois appliquer un lambda, c'est comme tout, la théorie ne fait pas la pratique en terme de connaissance sur un moment bien précis comme le cas d'utilisation des lambdas.
L'un des cas d'utilisation les plus fréquent pour les lambdas est sans doute l'utilisation des fonctionnalités issues du fichier d'en-tête <algorithms>.
A titre personnel, j'ai un peu de mal à envisager de les utiliser dans d'autres circonstances, mais bon, d'autres que moi sont peut être d'un avis différent
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
A titre personnel, j'ai un peu de mal à envisager de les utiliser dans d'autres circonstances, mais bon, d'autres que moi sont peut être d'un avis différent
Voir par exemple la présentation de J.Falcou à la CppCon sur les expressions templates (dans la dernière partie je crois).
J'aimerai revenir sur le lien que tu m'as passé et sur les evil#1, evil#2, evil#3, evil#4 :
Lorsqu'il parle de macro, l'auteur dit qu'une macro est pas bien, cependant il apprend en parallèle comment les faire, c'est un petit peut contradictoire (enfin je trouve personnellement).
Ensuite, dans le premier "evil" il soulève des points que j'aimerai bien éclaircir (je pense avoir compris mais j'ai quand même quelques doutes). La phrase que j'ai retenue le plus est la suivante :
Unlike #define macros, inline functions avoid infamous macro errors since inline functions always evaluate every argument exactly once. In other words, invoking an inline function is semantically just like invoking a regular function, only faster:
Voilà ce que j'ai compris. Les fonctions inline permette d'évaluer chaque argument une seule fois. Mais qu'entend-t-il par évaluer un argument ? Voilà ce que j'ai compris :
Une fonction par exemple où on lui passe : f(i++) va incrémenter le i qu'une seule fois (ce qu'il me semble logique en soi) mais une macro, elle, va incrémenter le i 2 fois étant donnée qu'une macro évalue les arguments 2 fois.
Et c'est là que je me pose des questions : Pourquoi une macro évalue l'argument 2 fois ? Voilà ma conjecture :
On sait qu'une macro n'a pas de type défini de base (quand on définie une macro on ne lui passe pas de type en paramètre). Donc le compilateur va évaluer la macro une première fois pour la mettre dans le code, puis une seconde fois pour vérifier si tout est bon. C'est ça ?
Par contre, pendant tout les autres evil, il dit que les macros sont à éviter le plus possible, pourtant, il explique quand même comment faire une macro correctement. Mais encore une fois il soulève pas mal de point intéressant.
#define NazaraSignal(SignalName, ...) using SignalName ## Type = Nz::Signal<__VA_ARGS__>; \
mutable SignalName ## Type SignalName
C'est pour ça que je n'aime pas les généralités de ce style:
koala01 a écrit:
Les macros ne sont JAMAIS la solution.
En revanche oui, je les déconseille le plus possible, et bien évidemment dans le cas de fonctions on a suffisamment d'outils bien meilleurs pour ne (presque?) jamais devoir les utiliser.
D'accord, je pensais quand même que le compilateur évalué l'expression de la macro avant de remplacer le texte pour éviter les erreurs. Donc si je comprends bien, on évite les macros car ça n'a pas la sémantique d'une fonction c'est bien ça ? (en partie bien entendu ).
Mais dans ce cas @Lynix, pourquoi ne pas avoir fait ta gestion d'erreur dans une fonction au lieu de passer par une macro ?
D'accord, je pensais quand même que le compilateur évalué l'expression de la macro avant de remplacer le texte pour éviter les erreurs. Donc si je comprends bien, on évite les macros car ça n'a pas la sémantique d'une fonction c'est bien ça ? (en partie bien entendu ).
On les évite parce que c'est dégueulasse, que ça provoque des erreurs, et qu'on a de meilleurs outils (quand on peut les éviter, cf mon message du dessus).
En revanche, tu dois savoir que le compilateur n'a aucune notion de macros, il ne les voit même pas, c'est le pré-processeur qui s'occupe du remplacement (et lui n'a aucune notion du code qu'il remplace).
Mais dans ce cas @Lynix, pourquoi ne pas avoir fait ta gestion d'erreur dans une fonction au lieu de passer par une macro ?
Ma macro appelle une fonction, mais je ne pouvais pas faire de fonction "NazaraError" car j'utilise les constantes __LINE__ et __FILE__ dont la position dans le code est importante.
On évites les macros lorqu'il est possible de le faire sans. Les macros restent indispensables dans plusieurs cas où le contexte compile-time importe comme dit plus haut où en guise de guard (typiquement les inclusions multiples).
En fait, les assertions sont un cas particulier du simple fait que les vérifications qu'elles font ne devront être présentes que lors du débugage (ou, du moins, lorsque l'on compile le projet en vue de pouvoir le débuger). Elles devront être remplacées par une "no-op" (par "rien du tout", pour faire simple) lorsque l'on compilera le projet en vue de diffusion
Du coup, quoi que l'on fasse, il faut pourvoir définir "ce qui doit être fait" en fonction du mode envisagé, et ca, ca entre parfaitement dans le cadre de la compilation conditionnelle que les macros sont (peu ou prou) la seule technique capable de nous permettre
De plus, le fait que, lorsque l'on compile en mode debug, le fait que la macro soit, effectivement, remplacé par le texte équivalent permet de s'assurer que le fichier dans lequel l'erreur apparait sera effectivement le fichier indiqué en cas d'erreur, alors qu'une fonction inline aurait eu pour résultat d'indiquer... le fichier dans lequel la fonction a été implémentée.
Donc, oui, effectivement : il y a quelques cas bien particuliers dans lesquels les macros sont intéressants / utiles / nécessaires / indispensables (en attendant les modules, on n'a pas encore trouvé mieux que les include guards pour éviter les inclusions multiples )
Mais, chaque fois qu'il y a moyen de faire autrement (pour définir une fonction inline, pour définir une valeur numérique, pour définir une interface, ...), il est très largement conseillé d'utiliser l'alternative aux macros
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
D'accord, donc pour résumé ça de façon assez "grossière", on évite les macros sauf si on doit faire de la gestion d'erreur au "run-time" ou "compile-time" (cf static_assert et assert).
Bien entendu, je pense qu'il y a d'autre moment où une macro est indispensable, mais je ne sais pas lesquels, j'ai donc pris ceux qui me semblaient les plus logiques.
D'accord, donc pour résumé ça de façon assez "grossière", on évite les macros sauf si on doit faire de la gestion d'erreur au "run-time" ou "compile-time" (cf static_assert et assert).
Bien entendu, je pense qu'il y a d'autre moment où une macro est indispensable, mais je ne sais pas lesquels, j'ai donc pris ceux qui me semblaient les plus logiques.
Cordialement,
On évite les macros si on a un meilleur outil à utiliser, point.
Parce que pour mon exemple avec les signaux, je défie quiconque de me trouver une meilleure façon de le faire (en tout cas moi j'ai pas trouvé).
if (0)
NazaraAssert(a, err);
else {
// jamais exécuté
}
#define NazaraAssert(a,err) do { ... } while(0)
> on évite les macros sauf si on doit faire de la gestion d'erreur au "run-time"
Pas vraiment, on les utilise quand on veut accéder au macro dynamique __FILE__, __LINE, __PRETTY_FUNCTION__, __func__, etc, sans avoir à les écrire.
Pour assert par exemple, on écrit assert(cond) à la place de assert(cond, __FILE__, __LINE__, __PRETTY_FUNCTION__).
> ou "compile-time" (cf static_assert et assert).
assert est post-compile-time et les macros pre-compile-time. Il faut bien comprendre que les macros sont un langage à part exécuté avant le compilateur.
Les macros me servent pour la compatibilité avec d'ancienne norme (CPP_NOEXCEPT remplacé ou non par noexcept) et à la génération de code C++ (quand des gros morceau de code (fonctions/classes) sont identique à quelques identifiants près (ex: des macros de vérification de présence de type)).
- Edité par jo_link_noir 27 octobre 2015 à 17:00:42
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
git is great because Linus did it, mercurial is better because he didn't.
git is great because Linus did it, mercurial is better because he didn't.
git is great because Linus did it, mercurial is better because he didn't.
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Mes articles | Nazara Engine | Discord NaN | Ma chaîne Twitch (programmation)
Mes articles | Nazara Engine | Discord NaN | Ma chaîne Twitch (programmation)
Mes articles | Nazara Engine | Discord NaN | Ma chaîne Twitch (programmation)
Mes articles | Nazara Engine | Discord NaN | Ma chaîne Twitch (programmation)
git is great because Linus did it, mercurial is better because he didn't.