France IOI: Qui a été sélectionné pour le stage du 6 au 12 juillet ?

Ah ? Alors, la SFINAE s'applique quand ? Qu'est-ce que une substitution failure, car d'après ce que j'en ai compris, cela me semble ceci?!

SFINAE désigne l'application de FDIS (n3290) §14.8.3 ¶1 :
> A function template can be overloaded either by (non-template)
> functions of its name or by (other) function templates of the same
> name. When a call to that name is written (explicitly, or implicitly
> using the operator notation), template argument deduction (14.8.2) and
> checking of any explicit template arguments (14.3) are performed for
> each function template to find the template argument values (if any)
> that can be used with that function template to instantiate a function
> template specialization that can be invoked with the call arguments.
> For each function template, if the argument deduction and checking
> succeeds, the template-arguments (deduced and/or explicit) are used to
> synthesize the declaration of a single function template
> specialization which is added to the candidate functions set to be
> used in overload resolution. If, for a given function template,
> argument deduction fails, no such function is added to the set of
> candidate functions for that template. The complete set of candidate
> functions includes all the synthesized declarations and all of the
> non-template overloaded functions of the same name. The synthesized
> declarations are treated like any other functions in the remainder of
> overload resolution, except as explicitly noted in 13.3.3.145

Alors, regardons une substitution simple :

1
2
        template<typename T, typename U> struct pair{ T first; U second; };
        pair<int,double> p;

La substitution est l'action de remplacer les template-parameters (ici T et U) par leur valeurs (ici int et double).
Cette substitution se fait (pour un grand nombre de compilateur, comeau étant une exception IIRC) directement sur l'AST. En gros, l'AST est copié et les T sont remplacés par des ints et les U par des doubles.

Si tu regardes les codes d'Ekleog, c'est simple :

1
2
3
4
5
6
7
        template <template <typename> class C>
        struct S { typedef typename C<int>::type type; };
        
        template <typename T>
        struct I { typedef T type; };
        
        typedef S<I>::type t;

On commence par chercher ce qu'est S\<I>::type :
template \<…C…> S::type est C\<int>::type .
Donc (substitution) S\<I>::type est I\<int>::type .
template \<…T…> I::type est T .
Donc (substitution) I\<int>::type est int .
Donc S\<I>::type est int .

Maintenant regardons une substitution failure :

1
2
3
4
        template <template <typename> class C>
        struct S { typedef typename C<int>::type type; };
        
        typedef S<S>::type type;

On commence par chercher ce qu'est S\<S>::type :
template \<…C…> S::type est C\<int>::type .
Donc (substitution) S\<S>::type est S\<int>::type .
template \<…C…> S::type est C\<int>::type .
Donc (substitution) S\<int>::type est int\<int>::type .
int n'est pas un class-templae. int\<int>::type n'existe pas. On ne peut pas faire la substitution… C'est une substitution failure ! (is an error ici par contre)
En fait, dans ce cas là le compilateur trouverait l'erreur plus tôt puisqu'ici, je n'ai pas typé les template-parameters (les « …C… »). C.f. mon dernier post.

Donc, voilà ce qu'est une substitution. Dans le dernier cas, j'ai appelé ça une substitution failure mais, pour être pointilleux, ce n'en est pas vraiment une… en effet l'erreur a lieu après la substitution.

Maintenant, le SFINAE ça s'utilise avec les fonctions.
Par exemple :

1
2
3
4
        template<typename T>
        void f(T::type){}
        
        void f(...){}

Ici, une fonction prenant un nombre variable d'argument et un fonction-template sont déclarés. Quand tu fais un appel à f(x), la fonction appelée dépend de l'argument x. Les fonctions à nombre d'argument variable (la seconde f(...)) sont toujours considérées comme les pires possibles, elle sont donc appelées seulement s'il n'y a aucune autre fonction viable.
Concrètement, le compilateur sélectionne l'ensemble des fonctions viables puis choisie la meilleure.
Si je fais struct C{ typedef double type; }; C x; f(x); Ce sera le premier f qui est appelé. En effet, les deux f sont viables, mais la seconde fonction f est « pire » que la première.
Si je fais int x; f(x);
La première fonction est essayée :
Substitution de T par int : « void f(int::type) ».
C'est une erreur, si vous écrivez ça dans un code C++, votre compilateur vous criera dessus en norvégien.
Mais ici, nous somme en train de chercher les fonctions viable pour un appel, donc SFINAE dit que le compilateur doit rester muet et ignorer cette fonction.
La substitution a échoué, mais ce n'est pas une erreur : SFINAE.
La seconde fonction est viable elle, elle sera donc appelée.

Une utilisation très connu du SFINAE est la détection de membre.
Par exemple je veux voir si une classe a un certain membre et en fonction de cela executer deux fonctions différentes (en C++03 pour le lulz) :

1
2
3
4
5
        struct fourty_two{ static const int answer=42; };
        struct four{ static const int random_number=4; };
        struct zero_x_deux_a{ static const int answer=0x2A; }
        void cool_func(){ std::cout << "I'm so cool\n"; }
        void not_so_cool_func(){ std::cout << "I suck\n"; }

Comment écrire une fonction-template qui appellera cool_func() si son argument possède un membre answer ?
En gros, il faudrait écrire (pas du C++) :

1
2
3
4
5
        template<typename T>
        void solution(T x){
                if(T a un member answer) cool_func();
                else not_so_cool_func();
        }

Pour faire cela on utilise SFINAE :

1
2
3
4
5
6
7
8
        template<typename T, T> struct chk;
        
        template <typename T> void solution(chk<int,T::answer>*){
                cool_func();
        }
        template <typename> void solution(...){
                not_so_cool_func();
        }

chk ne sert qu'à vérifier si le type est bien int.

Que se passe t-il quand on fait :
solution\<fourty_two>(0); ?
Le compilateur liste les fonctions viables :
La première est bonne chk\<int,T::answer> est valide.
La seconde est également viable.
Il choisie la meilleure : puisque la seconde est à nombre d'argument variable, elle est plus mauvaise que toute les autres. La première est donc appelée.
"I'm so cool\n" est affiché.

Que se passe t'il quand on fait :
solution\<four>(0); ?
Le compilateur liste les fonctions viables :
La première est mauvaise, T::answer, c'est à dire four::answer n'existe pas. C'est un substitution failure… mais ce n'est pas une erreur !
La seconde est viable.
Il choisie la meilleure (parmi le groupe de un résultant) : la seconde est appelée.
"I suck\n" est affiché.

TADA.

@epsilon012 >> Les 4 et 5 sont invalides, la seule valeur étant une pointer literal et une integral literal est 0.
Avec -fpermissive , ça passe. (Elle n'est pas un peu horrible, cette option ?)

>> "Maintenant regardons une substitution failure :
template \<template \<typename> class C>
struct S { typedef typename C\<int>::type type; };

typedef S\<S>::type type;

On commence par chercher ce qu'est S\<S>::type :
template \<…C…> S::type est C\<int>::type.
Donc (substitution) S\<S>::type est S\<int>::type.
template \<…C…> S::type est C\<int>::type.
Donc (substitution) S\<int>::type est int::type.
int n'est pas un type class. int::type n'existe pas."

A la fin de ton raisonnement on tombe sur un int::type, mais selon le miens, après les substitutions on tombe sur un int\<int>::type.

On a S\<int>::type

Comme : template \<…C…> S::type est C\<int>::type

On remplace le C par int et on tombe bien sur un int\<int>::type

A moins que tu considères que dans ce cas le compilateur cherche ce qu'est un int::type sans calculer ce qu'est un int\<int> (sauf s'il en a besoin, par exemple si le Classe::type dépend du paramètre template de Classe [Classe valant ici int] )

edit epsilon012 : les \< > !

epsilon> "Je ne veux donc pas voir un seul C++1y. Ce sera C++ Fudge Sandwich !"
C++ Titanic, c'est mieux ! (Et c'est reparti pour la flame war entre c++11 et c++0b...)

@lgorythme> "je n'ai compris que le premier exemple personnellement. [...] Corrige moi si je me trompe."
Je crois (celui là je n'en suis pas sûr du tout) que ça va causer une erreur de compilation. Parce qu'une même lambda déclarée deux fois n'aura pas forcément le même type. Donc le truc du decltype ne fonctionnera pas. Mais ne me crois pas sur parole, c'est à tester/vérifier dans le standard !

@lgorythme> "sans que la boucle "while" ait le moindre effet..."
Selon l'exemple, certaines boucles while peuvent tourner indéfiniment, et certaines peuvent être interrompues par un autre thread/mmapped register/DMA/autre.
Par contre, ceux où volatile est appliqué au pointeur pourront (selon les optimisation) tourner indéfiniment, même si le pointeur pointe vers un mmapped register modifié, parce que c'est le pointeur qui doit être rechargé, pas le pointé.

@lgorythme> "La nature récursive de la déclaration m'empêche de déterminer ce qui va se passer, mais étant donné que je tournerais en rond à l'infini si on me demandait la nature de l'alias, je crois que le compilateur planterait."
Faux. cf. epsilon.

@lgorythme> "typedef S >::type type;"
? (Remplaces tous les \< par des < pour un bel affichage ;-) )

@lgorythme> "Pour le dernier exercice j'ai la m\^me remarque à faire que ci-dessus."
Le dernier définit t = int.

OzVessalius> "Le premier test, renvoie un lambda qui renvoie un int, donc type de retour : std::function "
Faux. Une lambda qui renvoie un int peut avoir n'importe quel type. Pas forcément std::function. Même si on peut "caster" la lambda en std::function.

OzVessalius> "Je suis pas sûr, mais int::type = int car, la SFINAE permet ça."
=> ?
C'est juste une erreur de compilation, hein.
La SFINAE dira que S n'est pas instanciable, et va balancer une erreur parce qu'il n'y a pas de substitution valide possible.

OzVessalius> "Le septième test, [...]"
Bien :-)

OzVessalius> "Pour les volatiles, je connais pas trop, je sais juste que c'est utile en multithreading."
Pas seulement. Dans tous les endroits où la mémoire peut changer sans en informer le programme / le compilateur.

OzVessalius> "@Structs: Personnellement, j'utilise les structures exclusivement pour la sérialisation."
Connais-tu boost::serialize (ou nom similaire) ?
Sinon, une question bête : fais moi une classe "point", dont l'unique rôle est de conserver les coordonnées (entières) x, y et z, qui appartiennent à l'ensemble ]-oo ; +oo[ (Non, je n'ai pas de touche infini sur mon clavier). Fais le en respectant au maximum les critères OO.

epsilon> "Et vous vous trompez, le premier n'est pas valide…"
Pas mal. =D
Surtout que tu viens en citant le standard, ce que j'ai eu la paresse de faire.
Juste pour savoir... Tu as trouvé la réponse avant ou après de lire le standard ?

epsilon> "Les 2 et 3 sont valides et sont des boucles infinis."
Si un autre thread vient prendre l'adresse de i et la modifie, le 2 peut finir ou non selon le bon vouloir du compilateur, le 3 est censé être obligé de faire stopper la boucle. Pour prendre l'adresse de i et la modifier, gdb ?

epsilon> "Alors que « int i=1; int *p=&i; while(*p){} » peut se terminer."
=> ?
Là, je ne comprends pas. Le compilateur est parfaitement libre de faire du mem2reg, et là tu peux toujours attendre pour qu'il se termine. Non ?

epsilon> "Les 4 et 5 sont invalides, la seule valeur étant une pointer literal et une integral literal est 0."
Alors comment faire de l'arithmétique des pointeurs ? Simplement ajouter un static_cast ? (C'est le genre d'erreur bêtes dont je parlais en entrée. :-°)

epsilon> "La 6 est invalide, S est un template ayant un template template parameter et non un template template template parameter. :)"
Ah, c'est bien ce que je comptais faire, mais OzVessalius m'avait perturbé et je n'étais plus sûr de moi, merci ! =)

epsilon> "(et utilisez \< à la place de )."
Il suffit d'utiliser < ; le > fonctionne très bien sans un \< qui le match. ;)
Sinon, j'espère n'avoir pas fait trop d'erreurs en les corrigeant. :-°

@lgorythme> ">> La 7 est valide. > Pourquoi ? oO"
S ::type => I ::type => int
(en déroulant les étapes)

@lgorythme>
"D'où tenaient vous vos connaissances ?" -> De \~5 ans de c++ (tenez*)
"Vous avez fait une école d'ingénieurs ?" -> Non, je sors de terminale cette année, et epsilon est à la fac (enfin, je crois que c'était ce qu'il m'avait dit)
"comment faites vous ???" -> Je lis les ressources en anglais. Tiens, une série intéressante repérée sur dvpz, qui pourrait facilement faire l'objet d'une autre série de questions : https://randomascii.wordpress.com/category/floating-point/ (à lire de bas en haut, c'est trié par plus récent d'abord)

@lgorythme> "Il me semble qu'il n'existe pas de doc officielle sur le C++..."
Le standard :-°
Il y a une version juste avant celle du c++11 sur le site du WG21. Et il me semble qu'epsilon dispose du FDIS sur son site. (Il est maintenant protégé par un password sur le site du WG21).

epsilon> "Pour chaque exemple, je suppose que c'est la seule ligne. S'il y a une autre partie du code qui modifie les variables forcément, c'est différent."
Thx gdb. :-p

epsilon> "[citation de la norme]"
Intéressant. :-)

OzVessalius> "Le 7 l'est grâce à la SFINAE."
Tu en as après la SFINAE, non ? \^\^
cf. https://en.wikipedia.org/wiki/SFINAE pour voir ce qu'est vraiment la SFINAE. (Je n'ai pas lu, mais l'exemple semble coller.)
(En fait non, epsilon a probablement bien mieux expliqué !)

sam, 23/06/2012 - 12:27 — Sylvain CHIRON

> Avec -fpermissive, ça passe. (Elle n'est pas un peu horrible, cette
> option ?)
Si je compile avec mon super compilateur C& (que je viens d'inventer), ça marchera aussi, mais ce sera un code C&, pas C++. Peut-être que gcc le compile, cela signifie seulement que gcc ne respecte pas le standard C++.

sam, 23/06/2012 - 13:16 — @lgorythme

> A la fin de ton raisonnement on tombe sur un int::type, mais selon le
> miens, après les substitutions on tombe sur un int ::type.
En effet, mais bon, comme j'ai dit, en pratique la substitution n'aura pas lieu.
C'est corrigé.

sam, 23/06/2012 - 14:09 — Ekleog

> Je crois (celui là je n'en suis pas sûr du tout) que ça va causer une
> erreur de compilation. Parce qu'une même lambda déclarée deux fois
> n'aura pas forcément le même type. Donc le truc du decltype ne
> fonctionnera pas. Mais ne me crois pas sur parole, c'est à
> tester/vérifier dans le standard !
En effet, le type d'une lambda est partiellement défini dans le standard :

FDIS (n3290) §5.1.2 [expr.prim.lambda] ¶3

> The type of the lambda-expression (which is also the type of the
> closure object) is a unique, unnamed non-union class type -- called
> the closure type -- whose properties are described below. […]
En fait, il faut vraiment imaginer que la lambda est remplacée par une struct. Donc, oui, les types seront différents. gcc les appelles main::{lambda()#1}, main::{lambda()#2}, etc… (si elle sont dans la fonction main).
Mais bon, de toute façon, pas de lambda expression dans un decltype. :)

sam, 23/06/2012 - 14:09 — Ekleog

> Faux. Une lambda qui renvoie un int peut avoir n'importe quel type.
> Pas forcément std::function. Même si on peut "caster" la lambda en
> std::function.
Ce n'est pas vraiment « n'importe quel type », et ce n'est jamais std::function. :)
C.f. §5.1.2.

sam, 23/06/2012 - 14:09 — Ekleog

> Fais le en respectant au maximum les critères OO.
L'OOP c'est mal.

sam, 23/06/2012 - 14:09 — Ekleog

> Tu as trouvé la réponse avant ou après de lire le standard ?
Je n'avais pas le versé en tête, mais quand on sait comment sont traitées les lambdas par le compilateur, l'écriture decltype(lamda) parait plus que douteuse. Ensuite… c'est le deuxième paragraphe hein…

sam, 23/06/2012 - 14:09 — Ekleog

> Si un autre thread vient prendre l'adresse de i et la modifie, le 2
> peut finir ou non selon le bon vouloir du compilateur, le 3 est censé
> être obligé de faire stopper la boucle. Pour prendre l'adresse de i et
> la modifier, gdb ?
Attention, c'est un pointer sur une variable volatile qui… ne l'est pas en fait.

sam, 23/06/2012 - 14:09 — Ekleog

> Il y a une version juste avant celle du c++11 sur le site du WG21. Et
> il me semble qu'epsilon dispose du FDIS sur son site. (Il est
> maintenant protégé par un password sur le site du WG21).
n3290 :)

>> n3290 :)

C'est tout ce qu'il y a à savoir ? Ou tu en as d'autres des documents comme ça sur le standard ?

J'aimerais bien trouver une source sûr dessus - et plus particulièrement sur C++11...

Est-ce l'un des document de la norme sur le C++ ?

Est-ce LE document de la norme sur le C++ ?

Est-ce qu'il en existe d'autres qui associés à celui-ci apportent tout ce qu'il faut avoir ?

n3290 est le FDIS, c'est à dire le dernier document sur lequel le WG21 (le groupe de standardisation du C++ de l'ISO) a travaillé avant de publier le standard C++11.
Il y a sûrement quelques changement entre le FDIS et le standard mais ils sont minimes.
Si tu veux, tu peux acheter le standard : http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=50372 .
Mais le FDIS est suffisant. Par contre, le standard/FDIS n'est pas fait pour apprendre le langage, mais pour le définir.
Donc je ne pense pas qu'il te sera très utile. C'est comme utiliser un dictionnaire pour apprendre une langue.

Et oui, le standard est "sûr" : il n'y a pas d'erreur dans le standard, s'il y en a une, l'erreur est la norme. (bon parfois il y a des contradictions, mais c'est très rare).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Point
{
    int m_X;
    int m_Y;
    int m_Z;
    public:
    Point() : m_X(0), m_Y(0), m_Z(0)
    {

    }
    Point(int x, int y, int z) : m_X(x), m_Y(y), m_Z(z)
    {

    }
    int GetX() const { return m_X; }
    int GetY() const { return m_Y; }
    int GetZ() const { return m_Z; }
    void SetX(int x) { m_X = x; }
    void SetY(int y) { m_Y = y; }
    void SetZ(int z) { m_Z = z; }
};

Voilà, mais, je connais pas les ensembles ]-Infini ; [+Infini (J'ai pas compris cette histoire de crochets)
J'ai utilisé des int, donc ça veut dire qu'il y a une limite ]-(2\^32)/2 ; [(+2\^32)/2 Je suppose, qu'il faut utiliser une classe de type GigaInt ?

Epsilon, pourquoi l'Orienté Objet c'est mal ? Faut-il préférer le fonctionnelle ? Quel est le paradigme à préférer ?

D'après 5.1.2, note: A closure object behaves like a function object (Donc, il se comporte comme un objet de fonction ? Or, en C++11, c'est un std::function )
Sinon, A lambda-expression shall not appear in an unevaluated operand (Donc, on peut pas utiliser decltype sur ça)

epsilon> "L'OOP c'est mal."
Il faut que je lui demande de le faire pour lui montrer ses torts. :-) (D'ailleurs, il est tombé dans le panneau, cf. plus bas)

OzVessalius> En plein dans le panneau !

Les fonctions GetXYZ / SetXYZ, à elles deux, brisent totalement l'encapsulation.

En fait, il *n'y a pas* de "bonne" solution, qui encapsule... puisqu'il n'y a rien à encapsuler !

En effet, Point, ici, ne fait que contenir des données, et n'a aucun comportement.

(D'ailleurs, pour avoir des coordonnées sur ]-oo;+oo[, il te faudrait des double, non des int. Enfin, après, ça se discute, mais 0.5 ¢ ]-oo;+oo[ (Non, je n'ai pas non plus de symbole appartient sur mon clavier), et n'est pas représentable avec des int. Non, pitié, pas de virgule fixe !)

Une bonne solution (àmha) était :

1
2
3
struct point {
   double x, y, z;
};

"Epsilon, pourquoi l'Orienté Objet c'est mal ? Faut-il préférer le fonctionnelle ? Quel est le paradigme à préférer ?"
En fait, l'OO c'est mal... quand on reste coincé dessus.
Tous les paradigmes se valent, et l'OO est bien... tant qu'on est sur son domaine.
Le "point" était l'exemple que j'employais pour te faire comprendre que les principes de l'OO sont bien beaux, mais ne sont pas toujours *la* solution. Ici, c'est plus de l'orienté données.
Le fonctionnel est un paradigme intéressant... mais qui montre ses limites avec le nombre de copies effectuées. Bon, avec c++11 ça va mieux, mais... on n'a toujours pas de bon support du fonctionnel.

Bref, pour que tu comprennes bien l'exemple : Ici, l'utilisateur peut déjà faire ce qu'il veut avec le point. Il n'y a aucune vérification dans les setters, aucun calcul dans les getters. Donc on ne veut qu'un stockage bête et méchant de trois coordonnées.

J'espère être compréhensible !

Les getters/setters sont une abomination s'il ne s'agit que d'accéder à un attribut membre (dans la plupart des cas).

Un objet c'est avant tout un concept caractérisé par ses données (attributs) et les opérations qu'on lui applique (fonctions membres).

Ces fonctions sont, à l'extérieur (pour l'utilisateur de la classe, donc) un simple moyen d'intervenir sur un objet pour lui faire réaliser ce pour quoi il a été conçu. A l'intérieur elles contiennent toutes les instructions "plus bas-niveau" qui manipulent les données pour réaliser l'action.

Lorsque ces fonctions sont de simples accesseurs, elles nécessitent un contexte particulier pour justifier leur utilisation. Par ce que des getX(), setX(), etc... c'est très lourd, surtout si c'est pour au final faire la même chose qu'un accès direct à l'un des membres : aucun calcul n'est effectué !

En revanche si un Point peut être déplacé dans le repaire, il est tout à fait justifié de créer un membre :

void translater(const Vecteur vec);

En effet l'utilisateur n'a probablement pas envie de modifier manuellement chacune des coordonnées à la main lorsqu'il déplace un point.

L'OO c'est (selon moi) un moyen d'augmenter l'abstraction, d'augmenter le "niveau de programmation".
Si on peut afficher un cube en 3D en mois d'une centaine de lignes avec une bibliothèque appropriée c'est bien parce que les objets utilisés utilisent eux aussi d'autres objets "plus bas niveau" qui eux même utilisent d'autres objets "plus bas niveau", etc... le tout ayant l'air très court et très synthétisé pour l'utilisateur puisque l'encapsulation dissimule toutes les opérations "interne" pour ne laisser visible que leurs effets, en se débarrassant de la manière dont ces effets sont obtenus.

>> "Epsilon, pourquoi l'Orienté Objet c'est mal ? Faut-il préférer le fonctionnelle ? Quel est le paradigme à préférer ?"

Heu... ce genre de question me paraît vide de sens. L'OO ne s'oppose pas au fonctionnel (la preuve : OCaml !). Un paradigme c'est juste un style. Certains sont plus adaptés que d'autres à certaines opérations, au même titre qu'un langage est plus adapté qu'un autre pour tel ou tel autre type d'application. C'est plus une manière d'envisager un programme qu'autre chose (même si certains paradigmes peuvent se révéler trop restrictif dans certaines situations, d'où l'intérêt d'en avoir plusieurs).

@lgorythme>

En revanche si un Point peut être déplacé dans le repaire, il est tout à fait justifié de créer un membre :

void translater(const Vecteur vec);

Erreur bis.
Un point peut être translaté, subir une rotation, une homothétie, une symétrie, une similitude, une identité, peut former un polygone, une droite, un plan...
Heureusement qu'on ne met pas toutes ces méthodes comme membres !
(C'est d'ailleurs le gros souci avec std::string : elle a été mal pensée. Faute avouée à demi pardonnée.)

En fait, il ne faut pas obliger l'utilisateur à dépendre de ce dont il n'a pas besoin .
Être translaté n'est clairement pas de la responsabilité d'un point. Mieux vaudrait ici créer une fonction libre, qui profiterait des membres publics exposés par point.

Au passage, je viens de penser qu'une meilleur version pourrait être :

1
2
3
4
template <typename T = double>
struct point {
   T x, y, z;
};

Pour envisager tout de suite un changement du type de stockage du point.

sam, 23/06/2012 - 20:11 — OzVessalius

> Epsilon, pourquoi l'Orienté Objet c'est mal ? Faut-il préférer le
> fonctionnelle ? Quel est le paradigme à préférer ?
Le paradigme à préférer est celui qui convient. Je pense qu'en plus l'OO c'est mal parce que beaucoup de choses se traduisent mal en OO et en plus certaines choses sont tout simplement impossible à représenter. Donc les langages qui sont OO only (bonjour Java) posent problème imho.

sam, 23/06/2012 - 20:11 — OzVessalius

> D'après 5.1.2, note: A closure object behaves like a function object
> (Donc, il se comporte comme un objet de fonction ? Or, en C++11, c'est
> un std::function)
std::function est un class-template. Ses instanciations sont des classes dont les instanciations sont des function objects.
Donc std::function est donc loin d'être le function object.

Qu'est ce qu'un function object ?

WG21 FDIS (n3290) §20.8 [function.objects] ¶1

> A function object type is an object type (3.9) that can be the type of
> the postfix-expression in a function call (5.2.2, 13.3.1.1).²³⁰ A
> function object is an object of a function object type. In the places
> where one would expect to pass a pointer to a function to an
> algorithmic template (Clause 25), the interface is specified to accept
> a function object. This not only makes algorithmic templates work with
> pointers to functions, but also enables them to work with arbitrary
> function objects.
>
> note 230) Such a type is a function pointer or a class type which has
> a member operator() or a class type which has a conversion to a
> pointer to function.
Donc en fait… std::function est un (et non le) function object class template.

sam, 23/06/2012 - 21:36 — @lgorythme

> void translater(const Vecteur vec);
Vecteur& operator+=(Vecteur const &rhs);

sam, 23/06/2012 - 21:36 — @lgorythme

> L'OO c'est (selon moi) un moyen d'augmenter l'abstraction, d'augmenter
> le "niveau de programmation".
What? Non. L'OO c'est pas très “haut niveau”, par là je veux dire que la traduction en binaire n'est pas si compliqué, le terme d'“objet” existait déjà en C, c'est simple, en C et en C++, pratiquement toute zone mémoire (à l'exception de celle des fonctions, vt, etc…) est un objet :

WG21 FDIS (n3290) §1.8 [intro.object] ¶1

> The constructs in a C++ program create, destroy, refer to, access, and
> manipulate objects. An object is a region of storage. [ Note: A
> function is not an object, regardless of whether or not it occupies
> storage in the way that objects do. -- end note ] […]

WG21 FDIS (n3290) §1.7 [intro.memory] ¶3

> A memory location is either an object of scalar type or a maximal
> sequence of adjacent bit-fields all having non-zero width. [ Note:
> Various features of the language, such as references and virtual
> functions, might involve additional memory locations that are not
> accessible to programs but are managed by the implementation. -- end
> note ] […]

WG14 n1548 §3.15 ¶1

> object
> region of data storage in the execution environment, the contents of
> which can represent values

\<troll>En fait, le C c'est plus orienté objet que le Java puisque en Java un int n'est pas un objet. :p\</troll>

Je comprend ce que tu veux dire par augmenter l'abstraction, mais je ne suis pas d'accord. L'OO permet de représenter plutôt bien les données, mais pas leur relation.
Quand au problème du Point 3D… en fait :
struct Point3D { double x, y, z; };
Est OO… c'est juste que les opérations (type setX) ne sont pas traduites en C++ par des fonctions.
De toute façon, en C++, on a la programmation générique qui est bien plus expressive.

Terminons par une citation de Paul Graham :
> Object-oriented programming is popular in big companies, because it
> suits the way they write software. At big companies, software tends to
> be written by large (and frequently changing) teams of mediocre
> programmers. Object-oriented programming imposes a discipline on these
> programmers that prevents any one of them from doing too much damage.
> The price is that the resulting code is bloated with protocols and
> full of duplication. This is not too high a price for big companies,
> because their software is probably going to be bloated and full of
> duplication anyway.

dim, 24/06/2012 - 13:01 — Ekleog

> (C'est d'ailleurs le gros souci avec std::string : elle a été mal
> pensée. Faute avouée à demi pardonnée.)
+1

dim, 24/06/2012 - 13:01 — Ekleog

> Au passage, je viens de penser qu'une meilleur version pourrait être :

1
2
3
4
>         template <typename T = double>
>         struct point {
>         T x, y, z;
>         };

En fait, la meilleure version, c'est std::array (pour pouvoir choisir la dimension). :)

&gt &gt L'OO c'est (selon moi) un moyen d'augmenter l'abstraction, d'augmenter le "niveau de programmation".

&gt What? Non. L'OO c'est pas très “haut niveau”, par là je veux dire que la traduction en binaire n'est pas si compliqué, le terme d'“objet” existait déjà en C, c'est simple, en C et en C++, pratiquement toute zone mémoire (à l'exception de celle des fonctions, vt, etc…) est un objet :

Je ne qualifie pas l'OO de paradigme haut-niveau. J’essaie d'expliquer que son utilisation permet de faire comme si. Cf l’exemple que j'ai employé (sur le cube 3D en moins de 100 lignes). Un tel programme utilise différentes classes "Haut niveau", qui elles mêmes utilisent des classes plus bas niveau, etc... jusqu'à ce qu'on retombe sur les éléments les plus basiques du langage. C'est également valable lorsqu'on fonctionne avec des fonctions "haut-niveau" qui elles mêmes utilisent des fonction plus bas-niveau, etc... On atteint un certains niveau d'abstraction. En effet, on a pas besoin de connaître l'implémentation en "interne" d'une caméra pour pouvoir l'utiliser avec les fonctions membres appropriées. On la déplace, et puis c'est tout ! On ne se soucie pas de comment on la déplace.

&gt &gt void translater(const Vecteur vec);
&gt Vecteur& operator+=(Vecteur const &rhs);

Je propose :

void Vecteur::operator()(Point & p);

On utilise les vecteurs comme foncteur pour translater des points. Mieux : on en fait une fonction membre template pour translater des figures entières, etc... et pas seulement des points. L'utiliser comme foncteur permet de l'utiliser avec nombre des algorithmes de l'en-tête du même nom. Par exemple for_each() pour ne citer que lui. Est-ce une bonne idée de conception ?

dim, 24/06/2012 - 15:54 — @lgorythme

> Je propose :
>
> void Vecteur::operator()(Point & p);
>
> On utilise les vecteurs comme foncteur pour translater des points.
> Mieux : on en fait une fonction membre template pour translater des
> figures entières, etc... et pas seulement des points. L'utiliser comme
> foncteur permet de l'utiliser avec nombre des algorithmes de l'en-tête
> du même nom. Par exemple for_each() pour ne citer que lui. Est-ce une
> bonne idée de conception ?
Horrible. :p
Pourquoi une addition (translation) et pas autre chose ? Un vecteur est un objet, pas une opération, si tu as envie tu peux créer la classe "translator", mais je ne vois pas l'intérêt, tu veux ajouter un même vecteur à tout un container ? Tu peux utiliser std::transform avec une lambda ou même std::plus avec std::bind (ou std::bind1st en C++03).

dim, 24/06/2012 - 15:54 — @lgorythme

> Je ne qualifie pas l'OO de paradigme haut-niveau. J’essaie d'expliquer
> que son utilisation permet de faire comme si. […]
Sauf que tout cela n'est pas spécifique à l'OO. Ce que tu décris c'est l'abstraction, pas l'OO.

sam, 23/06/2012 - 14:09 — Ekleog

> epsilon> "(et utilisez \< à la place de \< et > à la place de >)."
> Il suffit d'utiliser < ; le > fonctionne très bien sans un \< qui
> le match. ;)
Je me demande si ce n'est pas à cause de ça que certains messages passent en spam en fait. :p

sam, 23/06/2012 - 14:09 — Ekleog

> Alors comment faire de l'arithmétique des pointeurs ? Simplement
> ajouter un static_cast ? (C'est le genre d'erreur bêtes dont je
> parlais en entrée. :-°)
L'arithmétique sur les pointeurs, c'est surtout des additions et des soustractions.
En fait, c'est assez logique… Si tu veux au lieu de :
int *p=0x42424242; // Invalide en C++
Tu peux faire :
int *p=0;
p+=0x42424242;

epsilon> "En fait, la meilleure version, c'est std::array (pour pouvoir choisir la dimension). :)"
Sauf qu'on y perd la sémantique associée à x, y et z. :/
Mais si le but est uniquement de représenter un ensemble de N scalaires, alors oui. (C'est d'ailleurs le but de std::array :D)

@lgorythme> "&gt &gt"
Inutile de mettre ça sans

"Cf l’exemple que j'ai employé (sur le cube 3D en moins de 100 lignes)."
OpenGL permet déjà de le faire en environ 100 lignes, hein. Et puis, des fonctions le font très bien, aussi.

epsilon> "Je me demande si ce n'est pas à cause de ça que certains messages passent en spam en fait. :p"
Ah, c'est possible. Où est le code du parser de messages du forum prologin, que j'y jette un coup d’œil ? :D

"L'arithmétique sur les pointeurs, c'est surtout des additions et des soustractions. [exemple]"
Sauf qu'on ne peut pas faire ça avec un constexpr. :/
(Oui, le fameux pointeur sur 0xB8000.)

D'ailleurs par rapport à ta remarque concernant constexpr, j'ai jamais compris à quoi sert constexpr ? On m'a dit que c'était un truc utile pour les templates. Mais je vois pas trop.

Une fonction constexpr est résolue à la compilation, si ses paramètres sont constexpr aussi.
Ce qui permet de faire quelque chose comme :

1
2
template <int I>
struct PlusUn { enum { value = I + 1 }; };

constexpr int fois42(int i) { return i * 42; }

assert(PlusUn\<fois42(1337)>::value == 1337 * 42 + 1);

Edit epsilon012 : les \< > !

Je suppose que c'est bien pour la méta-programmation.
J'avais déjà vu un code un peu dans le même style pour le déroulage d'un tuple.

Répondre au sujet

Vous devez vous enregistrer ou vous connecter pour poster des messages.