dans le cours de C++ de Mathieu sur les foncteurs, j'aurais besoin d'explicitations sur ce que j'ai mis en gras pour comprendre. Pourriez-vous me donner un exemple de ce qui est en caractère gras ?
Merci par avance :
"Ce que l'on aimerait faire, c'est appliquer des changements sur des conteneurs, par exemple prendre un tableau de lettres et toutes les convertir en majuscule. Ou prendre une liste de nombres et ajouter 5 à tous les nombres pairs. Bref, on aimerait appliquer une fonction sur tous les éléments d'un conteneur. Le problème, c'est qu'il faudrait pouvoir passer cette fonction en argument d'une méthode du conteneur. Et cela, on ne sait pas le faire. On ne peut passer que des objets en argument et pas des fonctions.
Techniquement, ce n'est pas vrai. Il existe des pointeurs sur des fonctions et l'on pourrait utiliser ces pointeurs pour résoudre ce problème. Les foncteurs sont par contre plus simples d'utilisation et offrent plus de possibilités."
Combien de fois faudra-t-il te le répéter? oublie ce putain de cours !!!
Mais donc, pour répondre à ta question, et en gardant bien en tête le fait que tout ce que je pourrai dire ici est devenu obsolète depuis plus de 7 ans:
Le problème, c'est qu'il faudrait pouvoir passer cette fonction en argument d'une méthode du conteneur. Et cela, on ne sait pas le faire.
Déjà, c'est complètement faux (du moins tel qu'indiqué et hors de tout contexte), car il est tout à fait possible de transmettre un pointeur de fonction (libre), et même de transmettre un pointeur de fonction membre (qu'elle soit statique ou non) d'une classe.
Simplement, c'est plus compliqué, et difficilement adaptable, car la signature des fonctions membres (qu'elles soient statiques ou non) nécessite d'avoir recours à leur nom pleinement qualifié (comprend: au nom de la classe dont elle est issue). Ainsi, s'il est possible de créer un pointeur de fonction libre sous une forme proche de
/* soit une fonction ne renvoyant rien et ne prenant aucun
* paramètre proche de
*/
void foo();
/* on peut créer un pointeur de fonction qui l'utilisera sous
* la forme de
*/
typedef void (*CALLBACK) ();
/* qui sera utilisé sous une forme proche de
int main(){
CALLBACK cb= foo;
cb();
}
la même chose avec une fonction membre nécessiterait un code proche de
/* soit une classe ou une structure */
struct MaStruct{
/* exposant une fonction membre statiques ne prenant
* pas de paramètres et en renvoyant aucune donnée
*/
static void foo();
};
/* on peut créer un pointeur de fonction sous la forme de
*/
typedef void (* MaStruct::CALLBACK)();
et l'utiliser sous une forme proche de
int main(){
CALLBACK cb = MaStruct::foo;
cb();
}
(par contre, les choses deviennent encore plus compliquée avec les fonctions membres non statiques parce qu'elle prennent un pointeur this comme premier paramètre automatiquement rajouté à la signature de la fonction par le compilateur).
L'idée est donc de créer ce que l' on peut appeler un "objet fonction" (ou un "foncteur") qui sera -- pour faire simple -- une structure (ou une classe) qui exposera l'opérateur () (car, oui, la paire de parenthèses correspond à un opérateur, qui prend la forme de générale de TYPE_DE_RETOUR operator () (/* paramètres requis*/); ). Cela pourrait donc prendre une forme proche de
struct Foncteur{
void operator() /* const */;
};
Et qui nous permettra de profiter du meilleur des deux mondes:
C'est une structure, et nous pouvons donc l'utiliser comme n'importe quelle strucutre :
on peut créer une variable dont le type est la structure
int main(){
Foncteur fct;
}
on peut le transmettre en paramètre à une fonction :
void foo(Foncteur const & fct);
On peut même créer des collections de fonceurs:
int main(){
std::vector<Foncteur> tab;
}
Mais, surtout, à partir du moment où l'on a un objet de type Foncteur, on peut invoquer l'opérateur() sous une forme proche de
int main(){
Foncteur fct;
fct();
}
/* et ca fonctionne quand on le recoit comme paramètre */
void foo(Foncteur const & fct)
Si le type de retour est un booléen, nous appellerons désignerons de préférence notre structure sous le terme de prédicat, et nous pouvons, bien sur, utiliser le paradigme générique pour déterminer le type des paramètres qui seraient transmis.
Ainsi, la SL nous offre par exemple le predicat less qui ressemble à quelque chose comme
template <typename T>
struct less{
bool operator()(T const & a, T const & b){
return a < b;
}
};
(et qui sert -- entre autre -- de type par défaut utilisé pour représenter le comparateur à utiliser dans les classes std::set et std::map, ainsi que dans les différents algorithmes de tri )
Maintenant, tout cela a été rendu obsolète en C++11 par l'apparition de std::function qui n'est -- finalement -- rien d'autre qu'une structure générique permettant de créer de foncteurs, avec tout ce que cela implique.
Ainsi, un code qui aurait pu ressembler à quelque chose comme
struct Multiplyer{
int operator(int i, int j) const{
return i * j;
}
};
struct Additioner{
int operator(int i, int j) const{
return i + j;
}
}
template <typename T>
void callIt(T const & t, int i, int j)){
std::cout<< t(i, j);
}
int main(){
accIt(Multiplyer(), 3, 5);
accIt(Additioner(), 3, 5);
}
pourra être écrit sous une forme proche de
int add(int i, int j){
return i + j;
}
int multiply(int i, int j){
return i * j;
}
void doIt(std::function<int (int, int)> const & f, int i, int j){
std::cout<<f(i,j);
}
int main(){
doIt(add, 3, 5);
doIt(multiply, 3 5);
/* voire meme, grâce aux expression lambda) */
auto a = [](int i, int j){ return i+j;};
auto b = [](int i, int j){ return i*j;};
doIt(a, 3, 5);
doIt(b, 3, 5);
}
(et, tu remarqueras que le deuxième code ne nécessite le même nombre de lignes que le premier, malgré le fait qu'il fait deux fois plus de choses )
- Edité par koala01 15 août 2018 à 15:39:13
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
j'ai envoyé un message à OC pour leur dire l'erreur.
(mouahahah)
koala01 a écrit:
Simplement, c'est plus compliqué
Précision : l'approche correcte pour passer une fonction comme argument, c'est de ne pas se casser les pieds et d'utiliser la déduction de types. (En C++ old school, d'utiliser les templates. C'est ce que font les algorithmes standards). Et cela permet d'etre tranquille pour n'importe quel type de callable (fonction, lambda, foncteur, etc).
La syntaxe pour passer une fonction membre est plus complexe... mais en fait, osef. On peut utiliser la meme syntaxe et ajouter une petite lambda et c'est bon.
#include <iostream>
void foo(auto&& f) {
f();
}
struct A {
void bar() { std::cout << "A::bar()" << std::endl; }
};
int main() {
A a;
foo([&a](){ a.bar(); });
}
Ca serait idiot de s'embeter a ecrire une surcharge de "foo" qui prend une fonction membre, surtout que la syntaxe est un peu moisi :
C'est pour cela que je pense que la programmation générique doit être appris assez tot. Ce n'est pas plus compliqué qu'autre chose et ca peut simplifier la vie.
Et a noter aussi : les foncteurs sont devenus pas mal obsoletes avec les fonctions lambdas.
Koala01, en relisant ton message, j'ai beaucoup ri avec ton expression "putain de cours". Perso, même si il est imparfait, j'ai un esprit critique qui me permet de sentir les bourdes. La perfection n'existe nulle part.
De plus, j'ai deux questions :
1) est-ce que le CALLBACK, c'est un truc pour les pointeurs sur fonction ?
2) est-ce que les pointeurs sur fonction, c'est quelque chose du C qui a été repris dans le C++ sans changement ?
Koala01, en relisant ton message, j'ai beaucoup ri avec ton expression "putain de cours". Perso, même si il est imparfait, j'ai un esprit critique qui me permet de sentir les bourdes. La perfection n'existe nulle part.
Je n'ai pas vraiment envie de revenir sur ce débat ...
Mais, maintenant que l'on te met de plus en plus "le nez dans la merde" que présente ce cours, as tu déjà essayé de barrer tous les passages concernés par les différents problèmes que l'on t'a signalé?
As tu déjà essayé de voir ce qu'il restait de ce "si merveilleux cours" après l'avoir fait?
Toi qui te vantes d'avoir un esprit critique, tu devrais peut-être penser à tenter l'expérience et de voir le ratio de ce qui est "à garder" et de ce que l'on t'a déjà dit de jeter.
De préférence en gardant à l'esprit que ce que l'on ne t'as pas dit de jeter n'a peut-être simplement pas encore trop attiré notre attention
Car je serais ma fois ... curieux de connaitre la réaction que t'inspire ton fameux "esprit critique" face à un tel ratio
(je n'ai pas tenté l'expérience, j'ai d'autres choses à foutre, aussi, si tu pouvais nous livrer le ratio en question en même temps que la réflexion que cela t'inspire, ce serait donc vraiment génial )
Mais, pour répondre à ta question:
YES, man a écrit:
1) est-ce que le CALLBACK, c'est un truc pour les pointeurs sur fonction ?
2) est-ce que les pointeurs sur fonction, c'est quelque chose du C qui a été repris dans le C++ sans changement ?
1 oui,
2 oui
- Edité par koala01 15 août 2018 à 18:13:52
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
Quand tu suis un cours, tu devrais garder ton esprit critique pour apprendre, au lieu de devoir savoir si le cours est juste ou non...
D'autant plus qu'on ne peut pas être et avoir été... Quand on apprend, on n'a -- clairement -- pas le recul nécessaire pour savoir si le cours qu'on reçoit est bon ou non.
Il faut "avoir été", avoir gagné sa propre expérience pour pouvoir se faire un avis qui ait la moindre valeur! Ici, la réaction de notre ami s'apparente à celle du teenager gâté qui refuse le conseil de ses parents uniquement parce que c'est, justement, l'avis de ses parents; sans se rendre compte que cet avis a été façonné au gré de leur expérience.
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
Pas la peine d'epiloguer encore une fois sur la question du cours. On previent les gens et ils font ce qu'ils veulent. C'est a eux d'assumer leurs choix.
YES, man a écrit:
Je ne suis pas du genre à être ingrat : ce cours m'a appris énormément de choses.
J'en suis conscient, et il est imparfait. Je suis aussi imparfait, Mathieu aussi. Donc juste un peu de tolérance.
EDIT : les arguments sont un peu idiot. On ne suis pas un cours par gratitude ou par tolérance.
Et surtout, tu insinues qu'on n'est pas respecteux de mathieu en critiquant son cours. Il faut un peu redescendre sur terre. On critique son cours parce qu'il est criticable. Et il le sait très bien.
Il faut arrêter de faire le bisounours. Quand on veut apprendre correctement (et certains ici jouent leur avenir professionel), on s'en donne les moyens.
Koala01 : je ne dis pas que tu as tort. C'est la nuance. Mais je n'insiste pas dessus.
Encore heureux que tu ne dises pas que j'ai tort... Car, loin de le prendre pour moi, je le prendrais pour tous les gens (professionnels ou non) qui on déjà essayé de te faire comprendre quelque chose sur ce forum, qui doivent totaliser près d'un siècle d'expérience à eux tous (si pas plus) et qui ont un avis similaire au mien.
YES, man a écrit:
Inutile de repartir sur des dénigrements.
Tu as sans doute raison, mais voyons un peu le point de vue qui m'incite à perdre mon sang froid et à taper mon poing sur la table (car c'est ce que je fais pour l'instant):
Nous t'avons dit et répété sur tous les tons possibles et imaginables ce que nous pensions de ce cours. Et non seulement, nous t'avons donné notre avis sur le sujet, mais nous l'avons justifié de cent manières différentes, y compris en pointant du doigts des passages entiers du cours que l'on décrie.
A tel point que je serais très sérieusement curieux de voir ce qu'il resterait de ce cours si nous devions barrer tous les passages qui t'ont personnellement été pointés du doigt comme aberrant, obsolètes ou incorrects. A mon sens, si on tentait l'expérience, nous ne devrions pas arriver à garder beaucoup plus de... vingt pourcents de ce qui fait le cours actuellement?
Et, malgré cela, tu persistes -- sous prétexte d'esprit critique -- à vouloir défendre ce cours bec et ongles; en dehors de tout sens critique, justement.
On dit que même un âne ne bute pas deux fois sur la même pierre. Mais toi, ce n'est pas deux fois que tu as buté dessus, mais sûrement dix fois, et sans doute même plutôt vingt fois, si pas d'avantage encore !
Et tu t'étonnes que l'on perde notre sang froid? Et tu t'étonnes que l'on ait un sourire nerveux ironique lorsque tu viens nous parler de ton "sens critique"?
Mais "mon ami", on ne récolte que ce qu'on sème! tu sèmes toi-même depuis près de deux ans (si pas plus) les graines de ta "décribilisation" . Ne t'étonnes pas si tu récolte un manque de crédibilité patent
- Edité par koala01 15 août 2018 à 18:53:47
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
et qui donnerait quelque chose de proche des fonctions membre en C++
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
Salut Koala01, avec ton message sur le CALLBACK, ça fait que je suis en train de me documenter sur cette notion de pointeur sur fonction.
Dans un tutoriel, j'arrive sur le sujet de "retourner un pointeur sur fonctions dans une de nos fonctions".
On donne l'exemple de code suivant:
int fonction1(void)
{
return 1;
}
int (* fonction2(void))(void)
{
return fonction1; /*Ici le retour d'un pointeur sur fonction*/
}
Ce que j'ai besoin de comprendre, c'est que selon ma compréhension , dans fonction2, quand on écrit "return fonction1", en principe on return un pointeur sur fonction (en fait une copie de quelque chose puisque la retour n'est pas fait par référence, mais par copie).
Alors que le type de retour de fonction2, n'est pas de type pointeur sur fonction, mais de type int.
Donc à priori, ça n'est pas consistant.
Deuxième chose : ce qui métonne, c'est que dans la fonction 2, on ait accès à la variable "fonction 1", dont je ne sais pas si à priori elle peut être considérée comme variable globale. Comment explique-t-on cela ?
Pourriez-vous m'expliquer comment comprendre cela de manière cohérente ?
> Alors que le type de retour de fonction2, n'est pas de type pointeur sur fonction, mais de type int.
Ah? Non. La syntaxe un peu étrange te trompe, mais c'est bien un pointeur de fonction qui est retourne.
> Donc à priori, ça n'est pas consistant.
Donc c'est tout a fait consistant.
> ce qui métonne, c'est que dans la fonction 2, on ait accès à la variable "fonction 1", dont je ne sais pas si à priori elle peut être considérée comme variable globale. Comment explique-t-on cela ?
La fonction est fonction1 est déclarée avant que fonction2 ne soit définie. Donc dans le corp de fonction2 le compilateur connais l'existence de fonction1. Ca te permet de l’appeler ou de récupérer son adresse. Exactement comme avec une variable.
Pour le CALLBACK dont parle Koala01, j'ai poursuivi mon tutoriel dessus. Et si je comprends :
dans les deux morceaux de code :
void foo();
/* on peut créer un pointeur de fonction qui l'utilisera sous
* la forme de
*/
typedef void (*CALLBACK) ();
/* qui sera utilisé sous une forme proche de
int main(){
CALLBACK cb= foo;
cb();
}
la même chose avec une fonction membre nécessiterait un code proche de
/* soit une classe ou une structure */
struct MaStruct{
/* exposant une fonction membre statiques ne prenant
* pas de paramètres et en renvoyant aucune donnée
*/
static void foo();
};
/* on peut créer un pointeur de fonction sous la forme de
*/
typedef void (* MaStruct::CALLBACK)();
et l'utiliser sous une forme proche de
int main(){
CALLBACK cb = MaStruct::foo;
cb();
}
CALLBACK n'est pas un mot-clé du C++, mais ici Koala01, tu définis toi-même un type que tu appelles CALLBACK et qui est un pointeur sur fonction ne prenant aucun paramètre et ne renvoyant rien non plus. N'hésitez pas à me dire si je fais erreur.
De même dans le deuxième code, il créé le type CALLBACK comme sous-type de la structure mystruct.
(koala01, je crois que tu as fait une tres grosse erreur en parlant des pointeurs de fonction Vouloir donner trop d'informations n'est pas pedagogique, ca fait perdre l'attention sur l'essentiel. Et a la fin, il n'aura rien retenu, et surtout pas la bonne syntaxe a utiliser)
Parfois on a besoin de manipuler des fonctions comme (comprendre: "via") des variables. En C il y a les pointeurs de fonctions. Dans les langages qui ne permettent pas cela (p.ex. Java), il y a le pattern Commande. En C++, il y a tout ça, et il y a des pointeurs de fonctions membres. La syntaxe du C++ est ce qu'elle est et n'offre pas de moyen syntaxique bas niveau uniforme pour stocker aussi bien des pointeurs de fonctions que des pointeurs de fonction membres.
On est donc partis sur des types plus complexes pour disposer de cette syntaxe unifiée. Cela a d'abord été boost::function, qui est devenu std::function -- il existe d'autres expérimentations qui n'ont pas été standardisées. Ça, c'est quand on a besoin de stocker et de se souvenir. Quand on a juste besoin de se paramétrer avec une fonction, il y a la syntaxe template qui permet de recevoir ce que l'on peut appeler non officiellement des callables -- i.e. tout ce qui peut s'écrire avec des parenthèses en plus (fonctions, ptr de fonctions, std::function, lambda, foncteurs ou plus précisément des objets fonctions, j'en oublie possiblement).
× 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.
Discord NaN. Mon site.
Discord NaN. Mon site.
GitHub
Discord NaN. Mon site.
Discord NaN. Mon site.
Discord NaN. Mon site.
Discord NaN. Mon site.