IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Spécialisation partielle de classe


Sujet :

Langage C++

  1. #1
    Futur Membre du Club
    Homme Profil pro
    Etudiant
    Inscrit en
    Juillet 2016
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Etudiant

    Informations forums :
    Inscription : Juillet 2016
    Messages : 11
    Points : 5
    Points
    5
    Par défaut Spécialisation partielle de classe
    Bonjour tout le monde !
    Je suis en train d'écrire une classe de vecteurs génériques de taille fixe.
    J'aimerais simplement pouvoir spécialiser les vecteurs de taille 2 et 3 pour pouvoir ajouter un accès aux coordonnées via
    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    vect.x;
    vect.y;
    vect.z;//Seulement si vect est de type Vector<T, 3>
    ,
    Si je veux spécialiser le template, je dois alors redéfinir toute l'interface de la classe qui est pourtant quasiment la même.
    Je ne sais pas bien comment faire cela autrement qu'avec un héritage qui me semble un peu lourd par rapport au besoin...
    Si quelqu'un a une suggestion je prend !
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    template<class T, unsigned int d> class Vector{
    private:
    	T coord[d];
    public:
    	Vector(const T (&coords)[d]){
    		memcpy(coord, coords, sizeof(T)*d);
    	}
    	template<typename... Ts>
    	Vector(Ts&&... coords) : coord{std::forward<Ts>(coords)...} {
    		static_assert(sizeof...(Ts) == d, "You must provide d arguments.");
    	}
    	// Tout plein de fonctions membres très utiles
    	// ...
    };
    // Les spécialisations suivantes ne sont évidement pas correctes !
    template<class T> class Vector<T, 2>{
    public:
    	T& x;
    	T& y;
    	Vector(const T (&coords)[2]): coord({coord[0],coords[1]}), x(coord[0]), y(coord[1]){}
    	Vector(T x, T y) : coord({x,y}), x(coord[0]), y(coord[1]) {}
    };
    template<class T>  class Vector<T, 3>{
    public:
    	T& x;
    	T& y;
    	T& z;
    	Vector(const T (&coords)[3]): x(coord[0]), y(coord[1]), z(coord[2]){
    		memcpy(coord, coords, sizeof(T)*3);
    	}
    	Vector(T x, T y, T z) : coord{x, y, z}, x(coord[0]), y(coord[1]), z(coord[2]) {}
    };

  2. #2
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    Bonjour,

    Tu ne veux pas vraiment spécialiser un template, tu peux veux effectivement partir d'une classe et la dériver pour en faire une classe semblable mais avec des trucs en plus (les membres x, y et z). Et ça, on dirait bien de l'héritage

  3. #3
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    On pourrait envisager de créer notre notion de tableau d'élément contigus en mémoire et de taille fixe sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    template <typename T, unsigned int S>
    class Vector{
    public:
        Vector(T value = T{}){
            for(auto & it : tab_)
                it=value;
    	}
     
        T& operator[](size_t index){
            assert(index < S && "index out of bound");
            return tab_[index];
        }
        T const & operator[](size_t index)const{
            return const_cast<Vector<T,2> &>(*this).operator[](index);
        }
    private:
        T tab_[S];
    };
    et envisager de fournir une spécialisation partielle pour les tableaux à deux et à trois éléments sous des formes proches de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    template <typename T>
    class Vector<T, 2>{
    public:
        Vector(T value = T{}):x{value},y{value}{
        }
        Vector(T x, T y):x{x},y{y}{
        }
        T x;
        T y;
        T & operator [](size_t index){
            assert(index < 2 && "index out of bound");
            if(index == 0)
                return x;
            return y;
        }	
        T const & operator [](size_t index) const{
            return const_cast<Vector<T,2> &>(*this).operator[](index);
        }
    };
    template <typename T>
    class Vector<T, 3>{
    public:
        Vector(T value = T{}):x{value},y{value}{	
        }
        Vector(T x, T y, T z):x{x},y{y},z{z}{
        }
        T x;
        T y;
        T z;
        T & operator [](size_t index){
            assert(index < 3 && "index out of bound");
            if(index == 0)
                return x;
            else if(index == 1)
                return y;
            return z;
        }	
        T const & operator [](size_t index) const{
            return const_cast<Vector<T,2> &>(*this).operator[](index);
        }
    };
    ce qui nous permettrait d'écrire de les utiliser sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    int main(){
        Vector<int,2> vec2{5,6};
        std::cout<<vec2.x<<" "<<vec2.y<<"\n";
        Vector<int,3> vec3{7,8,9};
        std::cout<<vec3.x<<" "<<vec3.y<<" "<<vec3.z<<"\n";
        return 0;
    }
    Le tout étant garanti fonctionner correctement et présenter une utilisation mémoire identique, à tout le moins pour les types primitifs (il faudrait vérifier ce qu'il en est pour des types plus complexes).

    La bonne question à se poser est: a-t-on réellement intérêt à le faire

    Car le fait est que là, nous mettons deux notions bien distinctes en compétitions, à savoir:
    1. la notion de tableau d'éléments contigus en mémoire d'une part et
    2. la notion de vecteur mathématique d'une autre

    La première entre dans la catégorie de ce que l'on appelle "les concepts généraux de conception", au même titre que les notions de tests ou de boucle, dans la sous-catégorie des "collections d'éléments", alors que la deuxième entre plutôt dans la catégorie de ce que l'on pourrait appeler "les concepts mathématique permettant de représenter des données complexes". Et tout le problème vient du fait que ces deux notions ne vont pas du tout se manipuler de la même manière

    Pire encore, la notion de collection (de manière générale) permet de représenter et de parcourir plusieurs données (d'un type particulier) susceptibles d'évoluer de manière totalement indépendantes, alors que la notion de vecteur mathématique permet de représenter une seule donnée complexe (comme la notion de force afin de déterminer son amplitude, sa direction et son sens) de manière "plus facilement compréhensible et manipulable", en représentant les différentes composantes de cette données sur différents axes. Chacune de ces composante ne prenant réellement du sens que parce qu'elle est associée aux autres composantes, la modification d'une seule de ces composantes modifiant systématiquement la valeur de la notion complexe représentée.

    D'ailleurs, c'est bien simple, la notion de vecteur supporte, a priori, parfaitement les opérations mathématiques (addition, soustraction, multiplication et division), alors que ces opérations n'ont pas vraiment de sens pour la notion de collection.

    Je t'ai donc démontré au début de cette intervention qu'on dispose des moyens de le faire sans problème majeur. Mais ce n'est pas parce que l'on est en mesure de faire quelque chose que c'est forcément une bonne idée de le faire. Dans le cas présent, je ne crois sincèrement pas que ce soit le cas d'un point de vue conceptuel

    De mon point de vue, tu aurais largement intérêt à représenter les notions associées aux vecteurs mathématique d'une part, par exemple, sous la forme de structures templates proches de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    template <typename T>
    struct Vec2{
        Vec2():Vec2(T{},T{}){
        }
        Vec2(T x, T y):x{x},y{y}{
        }
        T x;
        T y;
    };
    template <typename T>
    struct Vec3{
        Vec3():Vec3(T{},T{}, T{}){
        }
        Vec3(T x, T y, T z):x{x},y{y},z{z}{
        }
        T x;
        T y;
        T z;
    };
    /* il faudrait ajouter les fonctions permettant de les manipuler ;) */
    et, d'une autre part, de représenter ta notion de tableau d'éléments contigus en mémoire et de taille fixe sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    template <typename T, unsigned int S>
    class Vector{
    public:
        Vector(T value = T{}){
            for(auto & it : tab_)
                it=value;
    	}
     
        T& operator[](size_t index){
            assert(index < S && "index out of bound");
            return tab_[index];
        }
        T const & operator[](size_t index)const{
            return const_cast<Vector<T,2> &>(*this).operator[](index);
        }
    private:
        T tab_[S];
    };
    (note au passage que, depuis l'arrivée de C++11, qui date déjà de huit ans, nous disposons de la classe std::array, qui fait exactement ce genre de taf )
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  4. #4
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Bktero Voir le message
    Bonjour,

    Tu ne veux pas vraiment spécialiser un template, tu peux veux effectivement partir d'une classe et la dériver pour en faire une classe semblable mais avec des trucs en plus (les membres x, y et z). Et ça, on dirait bien de l'héritage
    Non, c'est, effectivement une forme de spécialisation. Ou, du moins, cela peut être considéré comme une forme de spécialisation, qui n'a aucun besoin d'avoir recours à l'héritage. Mais le fait, c'est que cette spécialisation est foireuse, ainsi que je l'ai exprimé en long et en large
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #5
    Futur Membre du Club
    Homme Profil pro
    Etudiant
    Inscrit en
    Juillet 2016
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Etudiant

    Informations forums :
    Inscription : Juillet 2016
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Merci pour ta réponse
    Du coup avec un héritage ça donne ça:
    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    template<class T, unsigned int d> class Vector;
    template<class T, unsigned int d> class VectorGeneric{
    protected:
    	T coord[d];
    public:
    	VectorGeneric(const T (&coords)[d]){
    		memcpy(coord, coords, sizeof(T)*d);
    	}
    	template<typename... Ts>
    	VectorGeneric(Ts&&... coords) : coord{std::forward<Ts>(coords)...} {
    		static_assert(sizeof...(Ts) == d, "You must provide d arguments.");
    	}
    	// Un exemple de fonction membre
    	Vector<T,d> operator+(const VectorGeneric<T,d>& vector) const{
    		T coords[d];
    		for(uint i=0; i<d; ++i){
    			coords[i]=vector.coord[i]+coord[i];
    		}
    		return Vector<T,d>(coords);
    	}
    };
    template<class T, unsigned int d> class Vector: public VectorGeneric<T, d>{
    		Vector(const T (&coords)[d]):VectorGeneric<T, d>(coords){}
    		template<typename... Ts>
    		Vector(Ts&&... coords) : VectorGeneric<T, d>(std::forward<Ts>(coords)...) {}
    };
    template<class T> class Vector<T, 2>: public VectorGeneric<T, 2>{
    public:
    	T& x = this->coord[0];
    	T& y = this->coord[1];
    	Vector(const T (&coords)[2]): VectorGeneric<T, 2>(coords){}
    	Vector(T x, T y) : VectorGeneric<T, 2>(x,y){}
    };
    template<class T>  class Vector<T, 3>: public VectorGeneric<T, 3>{
    public:
    	T& x = this->coord[0];
    	T& y = this->coord[1];
    	T& z = this->coord[2];
    	Vector(const T (&coords)[3]): VectorGeneric<T, 3>(coords){}
    	Vector(T x, T y, T z) : VectorGeneric<T, 3>(x, y, z){}
    };
    Ce qui n'est pas si cracra que ça mis à part le type de retour de VectorGeneric::operator+ qui devrait logiquement être de type VectorGeneric<T, d>.
    Il faudrait pouvoir restreindre la porté cette classe au fichier mais je ne crois pas que ce soit faisable même en utilisant des namespaces annonymes .

    EDIT: Je n'avais pas vu les nouvelles réponses !

  6. #6
    Futur Membre du Club
    Homme Profil pro
    Etudiant
    Inscrit en
    Juillet 2016
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Etudiant

    Informations forums :
    Inscription : Juillet 2016
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Je suis bien d'accord avec toi @koala01 mais tes propositions ne résolve pas du tout mon problème car ce que je veux éviter c'est le code dupliqué. Et si je travaille avec ce que tu proposes, toutes mes méthodes qui fonctionnent avec un vecteur de taille quelconque doivent alors être redéfinie pour les vecteurs de taille 2 et 3.
    L'idée de départ c'est simplement d'ajouter des "poignées" pour une utilisation plus orientée structure (mais ce n'est évidement qu'un artifice !, les données étant toujours stockées sous la forme d'un tableau.)

  7. #7
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Hellinbox Voir le message
    Je suis bien d'accord avec toi @koala01 mais tes propositions ne résolve pas du tout mon problème car ce que je veux éviter c'est le code dupliqué. Et si je travaille avec ce que tu proposes, toutes mes méthodes qui fonctionnent avec un vecteur de taille quelconque doivent alors être redéfinie pour les vecteurs de taille 2 et 3.
    Allons, soyons un peu logique: quelle différence y aurait il entre une collection qui contient cinq éléments et une collection qui n'en contient que deux ou trois

    Dis toi bien que les notion x, y et z n'ont rien à faire dans une collection d'éléments!

    Ou bien, tu manipules un tableau d'éléments contigus en mémoire à l'aide de l'opérateur [], voire, à l'aide de la notion d'itérateurs et des fonctions begin() et end() ou bien tu manipule un vecteur mathématique, en donnant une sémantique particulière (x, y et éventuellement z) à chacune de ses composante. Au pire, tu peux même fournir des fonctions de conversions de l'un vers l'autre.

    Mais, vu que tu veux manipuler deux notions totalement différentes, tu n'as pas beaucoup le choix : tu dois travailler sur deux types de données totalement différents. Et tant pis pour la duplication du code.
    Citation Envoyé par Hellinbox Voir le message
    L'idée de départ c'est simplement d'ajouter des "poignées" pour une utilisation plus orientée structure (mais ce n'est évidement qu'un artifice !, les données étant toujours stockées sous la forme d'un tableau.)
    Dans ce cas, ce que tu dois faire, ce n'est pas une spécialisation de ta notion de tableau, mais bel et bien définir les notions de vecteur mathématiques (à deux ou à trois composantes), sans mettre en place la substituabilité inhérante à l'héritage, sous une forme qui serait proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    template <typename T>
    class Vec2{
    public:
        Vec2():Vec2(T{}, T{}){
        }
        Vec2(T x, T y){
            data[0]=x;
            data[1]=y;
        }
        T & x(){
            return data[0];
        }
       T & y(){
            return data[1];
       }
        T const & x() const{
            return data[0];
        }
       T const & y() const{
            return data[1];
       }
    private:
        Vector<T,2> data;
    };
    template <typename T>
    class Vec3{
    public:
        Vec3():Vec3(T{}, T{}, T{}){
        }
        Vec3(T x, T y, T y){
            data[0]=x;
            data[1]=y;
            data[2]=y;
        }
        T & x(){
            return data[0];
        }
       T & y(){
            return data[1];
       }
       T & z(){
            return data[2];
       }
        T const & x() const{
            return data[0];
        }
       T const & y() const{
            return data[1];
       }
       T const & z() const {
            return data[2];
       }
    private:
        Vector<T,3> data;
    };
    Dis toi bien que, si, sous prétexte d'éviter la duplication de code (ou, plus fallacieux encore, de respecter le DRY), tu en viens à prendre des décisions conceptuellement incorrectes (comme le fait de regrouper les notions de tableaux et de vecteurs mathématique dans une seule classe), cela ne pourra jamais rien faire d'autre que de te péter à la figure à un moment ou à un autre (et, de préférence, au pire moment qui soit).

    Dis toi bien que C++ compte très certainement parmi les langages de programmation qui offrent la plus grande liberté technique. Seulement, cette liberté technique vient de pair avec un risque majeur : celui de te tirer une balle dans le pied. Le seul moyen d'éviter ce risque est d'avoir une conception absolument parfaite, car la seule conception te permettra de faire le tri entre ce que tu peux effectivement faire et ce qu'il vaut mieux éviter.

    Si tu t'écartes un tant soit peu d'une conception parfaite, si chaque décision n'est pas
    • correctement justifiée (du point de vue conceptuel)
    • limitée à un domaine le plus réduit possible lorsqu'elle s'écarte des règles de conception

    tu cours systématiquement à la catastrophe.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  8. #8
    Futur Membre du Club
    Homme Profil pro
    Etudiant
    Inscrit en
    Juillet 2016
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Etudiant

    Informations forums :
    Inscription : Juillet 2016
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Je ne suis pas certain d'avoir compris ce que tu m'expliques. J'ai bien compris la partie sur la duplication de code et c'est vrai que je ne devrais pas toujours adapter l'architecture pour éviter des redondances.

    Après là où je en te suis pas, c'est à propos des collections. Il s'agit, qu'il y ait des "poignées" ou non, de vecteur au sens mathématique du terme et non de collection, le tableau n'étant présent que par la facilité de manipulation qu'il représente; si j'ai un vecteur de dimension 500 je ne trouve pas d’intérêt à avoir un nom spécifique pour chacune des coordonnées. Ils peuvent par exemple être utilisé pour la résolution d'équation différentielle d'ordre élevée ou bien encore pour représenter une fonction discrétisée (et qui peut donc être additionnée avec une autre fonction ou bien multiplié par une constante). Il s'agit donc véritablement des mêmes type d'objet (au sens mathématique). Les poignées que je veux ajouter ne sont qu'une commodité et ne change absolument rien au comportement. Ils symbolisent simplement de façon plus marquée la projection sur un axe.

    En bref je ne comprend pas en quoi ce sont des objets différents.

    Sinon j'aime bien l'encapsulation que tu proposes, je vais sûrement m'en inspirer si je ne trouve pas mieux .

  9. #9
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Hellinbox Voir le message
    Après là où je en te suis pas, c'est à propos des collections. Il s'agit, qu'il y ait des "poignées" ou non, de vecteur au sens mathématique du terme et non de collection, le tableau n'étant présent que par la facilité de manipulation qu'il représente;
    C'est justement là qu'est tout le problème! Si tu parle de vecteur au sens mathématique, tu écarte d'office le fait qu'il puisse s'agir d'une collection, car l'utilisation et le conditions imposées par l'un sont en complete contradiction avec l'utilisation et les conditions imposées par l'autre.

    Au mieux, comme tu le dis si bien, un vecteur mathématique peut être implémenté sous la forme d'un tableau (ou d'une collection quelconque) pour faciliter les manipulations. On peut même envisager de mettre en place des mécanismes de conversions de l'un vers l'autre. Mais on ne peut en aucun cas envisager la substituabilité (le fait de transmettre un tableau à une fonction qui s'attend à recevoir un vecteur mathématique, ou inversement)
    si j'ai un vecteur de dimension 500 je ne trouve pas d’intérêt à avoir un nom spécifique pour chacune des coordonnées.
    Et tu n'en as, de fait, pas forcément besoin

    Mais quel type de vecteur mathématique pourrait nécessiter d'avoir cinq cent données différentes et pourtant interdépendantes? Dans quelle mesure ces cinq-cents données ne correspondent-t-elles pas à la courbe (dans le temps ) obtenue par la résultante de calculs effectués sur des vecteur à deux ou à trois données

    Dans quelle mesure as tu réellement un vecteur mathématique avec autant de donnée, et non un tableau (une collection) de vecteur mathématique "moins complexes"
    Ils peuvent par exemple être utilisé pour la résolution d'équation différentielle d'ordre élevée ou bien encore pour représenter une fonction discrétisée (et qui peut donc être additionnée avec une autre fonction ou bien multiplié par une constante).
    Et là, tu me démontre une fois encore que tu confond collection et vecteurs mathématiques, parce que ces données seront représentée sans doute sous la forme d'une matrice (au sens mathématique) qui sera elle-même composée "d'un certains nombre" de vecteurs

    Il s'agit donc véritablement des mêmes type d'objet (au sens mathématique). Les poignées que je veux ajouter ne sont qu'une commodité et ne change absolument rien au comportement. Ils symbolisent simplement de façon plus marquée la projection sur un axe.
    J'ai bien compris cela

    Mais il n'empêche que tu te heurtes à deux notions totalement différentes qui méritent donc d'être représentées dans le code de manière totalement différentes pour éviter les mauvaises compréhensions.

    La règle en développement, lorsqu'il s'agit de traduire une analyse fonctionnelle en code, pourrait se réduire à
    un nom (groupe nominal) == une donnée ou un type de donnée
    un verbe (groupe verbal) == une fonction
    Si tu veux t'éviter un maximum de soucis, tu as vraiment intérêt à la respecter "à la lettre" car, autrement, tu finira par te retrouver avec très peu de classes qui font tellement de choses que tu n'oses plus aller les modifier, de peur de tout casser

    Comme on parle d'une collection (un tableau) d'une part, et de vecteur mathématique de l'autre, tu as -- réellement -- tout intérêt à représenter ces deux notions de manière totalement distincte dans ton code
    En bref je ne comprend pas en quoi ce sont des objets différents.
    Un objet ne présente un intérêt qu'au travers des manipulations qu'il nous autorise : ton marteau, ton tournevis, ta foreuse ne te sont utiles que parce qu'ils te permettent d'entreprendre des actions totalement différentes.

    Il en va de même avec les notions de tableau et de vecteur mathématique : les tableau permettent de regrouper plusieurs données de même type mais indépendantes les unes des autres, alors que les vecteurs mathématiques permette de créer un "tout unique" qui va bien plus loin que la valeur des différents éléments qui le composent.

    Imaginons un vecteur mathématique vec(3,5). Tu es d'accord que, si je modifie ne serait-ce qu'un tant soit peu une seule des donnée, pour obtenir un vec(4,5) ou un vec(3,4), j'obtiens bel et bien un vecteur totalement différent, peu importe la notion qu'il permet de représenter (force, vitesse, accélération, ...)

    Si j'ai un tableau, qui contient les valeurs [3,5,10,15], et que je décide de modifier une (ou plusieurs) de ces valeurs pour obtenir les valeurs [5,10,15,20], j'obtiens un tableau qui contient ... le même nombre de données, mais avec des valeurs différentes. Et l'interprétation que l'on peut faire des données s'arrête là.

    Note, d'ailleurs, que j'aurais pu décider de créer un tableau de vecteur (qui n'a rien à voir avec les matrices mathématiques) qui prendrait la forme de [(1,2),(3,4),(5,6)] parce que j'aurais estimé, à un moment donné, intéressant de regrouper trois vecteurs sous une forme qui me permet d'accéder à chacun d'eux de manière indépendante.

    Mais ce tableau de vecteurs mathématique, même s'il peut être utilisé pour faciliter la représentation de la notion de matrices mathématiques, car je dois pouvoir, à partir d'une matrice,
    1. décider de l'inverser
    2. calculer le déterminant
    3. la transposer
    4. l'additionner à une autre
    5. la multiplier avec une autre
    6. ...

    Et ce sont autant de manipulations qui n'ont absolument aucun sens pour ... un tableau de vecteurs mathématiques
    Sinon j'aime bien l'encapsulation que tu proposes, je vais sûrement m'en inspirer si je ne trouve pas mieux .
    Le terme est lancé : encapsulation ! Le but de l'encapsulation est -- tout simplement -- de permettre à l'utilisateur d'une classe (ou d'une donnée) de n'avoir pas à s'inquiéter de la manière dont les données sont effectivement représentées en ne s'inquiétant que de la manière dont il peut les manipuler.

    On peut même aller plus loin et dire que les données ne servent qu'à permettre aux différents comportements (les fonctions, pour faire simple) de fournir un résultat cohérent, reproductible et prédictible, et que l'utilisateur (de la donnée et des comportements) n'a donc même pas besoin de savoir à quoi la donnée ressemble dans le programme.

    Je t'ai montré deux manières différentes de représenter la notion de vecteur 2D. Je pourrais encore sans doute t'en trouver d'autres. En les adaptant un tout petit peu, je pourrais faire en sorte qu'elle réagissent exactement de la même manière, ce qui ferait passer le fait qu'il utilise (ou non) un tableau en interne pour "un vulgaire détail d'implémentation" indigne d'attirer l'attention de l'utilisateur de mon vecteur.

    La preuve :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    template <typename T>
    class Vec2{
    public:
        Vec2():Vec2(T{}, T{}{
        }
        Vec2(T x, T y):x_{x},y_{y}{
        }
        T & x() {
            return x_;
        }
        T & y() {
            return y_;
        }
        T const & x() const{
            return x_;
        }
        T const & y() const {
            return y_;
        }
    private:
        T x;
        T y;
    };
    /* compare le fonctionnement de la classe ci-dessus à celui de la classe que voici */
    template <typename T>
    class Vec2{
    public:
        Vec2():Vec2(T{}, T{}){
        }
        Vec2(T x, T y){
            data[0]=x;
            data[1]=y;
        }
        T & x(){
            return data[0];
        }
       T & y(){
            return data[1];
       }
        T const & x() const{
            return data[0];
        }
       T const & y() const{
            return data[1];
       }
    private:
        Vector<T,2> data;
    };
    L'utilisateur de ces deux classes pourra utiliser une version ou l'autre sans avoir le moindre besoin de savoir comment elle fonctionne en interne. Il se peut même que je commence par utiliser la première version de cette classe dans mon projet et que, dans trois mois ou dans un an, je décide de passer à la deuxième version. Et, pourtant, le code que l'utilisateur aura écrit ne devra pas subir le moindre changement.

    Voici d'ailleurs peut-être la meilleure raison de respecter la règle que je te citais plus haut: en définissant chaque notion sous la forme d'un type de donnée différent, tu t'évites bien des problèmes le jour où tu décidera -- pour une raison ou une autre -- de représenter la notion de manière différente.

    Si tu persistes à vouloir représenter te vecteurs mathématique sous la même forme que tes tableaux, il arrivera toujours un moment où tu te diras qu'il aurait pu être sympa de changer la manière dont les vecteurs fonctionne, pour gagner en performances, par exemple, et où tu regretteras de ne pas pouvoir le faire parce que ... cela impliquerait de modifier la manière dont les tableaux fonctionnent, et que ca, c'est décidément impossible.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  10. #10
    Futur Membre du Club
    Homme Profil pro
    Etudiant
    Inscrit en
    Juillet 2016
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Etudiant

    Informations forums :
    Inscription : Juillet 2016
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Citation Envoyé par koala01
    Mais quel type de vecteur mathématique pourrait nécessiter d'avoir cinq cent données différentes et pourtant interdépendantes? Dans quelle mesure ces cinq-cents données ne correspondent-t-elles pas à la courbe (dans le temps ) obtenue par la résultante de calculs effectués sur des vecteur à deux ou à trois données ?
    Si je reconstruit un signal à partir d'une série de Fourrier, il peut être très intéressant de pouvoir ajouter deux vecteurs de fonction discrétisée (ou bien pour pouvoir simuler le fonctionnement d'une radio par exemple) et ces données ne proviennent pas forcément d'un calcul vectoriel mais d'un fichier voir même d'une antenne !
    Un autre exemple: on peut avoir un vecteur contenant les coefficients d'un polynôme de degrés élevé. Ça a d'autant plus de sens que Pn (l'ensemble des polynômes de degrés n et moins) est un espace vectoriel.
    Citation Envoyé par koala01
    Et là, tu me démontre une fois encore que tu confond collection et vecteurs mathématiques, parce que ces données seront représentée sans doute sous la forme d'une matrice (au sens mathématique) qui sera elle-même composée "d'un certains nombre" de vecteurs
    Ici je ne comprend pas pourquoi tu parles de matrice ^^' , dans le cas d'une équation différentielle de degré 5 il s'agit d'un vecteur de dimension 5 (de pointeur sur fonction ou bien de classe représentant une une fonction par exemple) et dans le cas d'une fonction discrétisée, il s'agit simplement d'un vecteur de nombre.

    Tu me dis si je me trompe: En gros, une fonction discrétisée devrais être représentée par une collection car d'après toi elle n'a pas les mêmes propriétés qu'un vecteur? Dans ce cas je ne comprend pas lesquelles car un tel objet vérifie tout les critères pour être un vecteur. Dans certain cas je suis entièrement d'accord, une collection suffit si l'on n'a pas de calculs typiquement vectoriel à effectuer mais dans le cas contraire je trouve ça dommage de s'en priver !
    Il en va de même avec les notions de tableau et de vecteur mathématique : les tableau permettent de regrouper plusieurs données de même type mais indépendantes les unes des autres, alors que les vecteurs mathématiques permette de créer un "tout unique" qui va bien plus loin que la valeur des différents éléments qui le composent.
    Je suis d'accord ! C'est bien pour cela qu'utiliser un vecteur pour représenter un sinus discrétisé est loin d'être idiot !
    Note, d'ailleurs, que j'aurais pu décider de créer un tableau de vecteur (qui n'a rien à voir avec les matrices mathématiques) qui prendrait la forme de [(1,2),(3,4),(5,6)] parce que j'aurais estimé, à un moment donné, intéressant de regrouper trois vecteurs sous une forme qui me permet d'accéder à chacun d'eux de manière indépendante.

    Mais ce tableau de vecteurs mathématique, même s'il peut être utilisé pour faciliter la représentation de la notion de matrices mathématiques, car je dois pouvoir, à partir d'une matrice,

    décider de l'inverser
    calculer le déterminant
    la transposer
    l'additionner à une autre
    la multiplier avec une autre
    ...


    Et ce sont autant de manipulations qui n'ont absolument aucun sens pour ... un tableau de vecteurs mathématiques
    Là je suis entièrement d'accord !

    Pour conclure, je crois que je manque le point de ce que tu veux me faire comprendre...

  11. #11
    Futur Membre du Club
    Homme Profil pro
    Etudiant
    Inscrit en
    Juillet 2016
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Etudiant

    Informations forums :
    Inscription : Juillet 2016
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Et un dernier point: De toute façon, peu importe l'utilisation que en sera faite derrière, je voudrais simplement faire une classe vecteur générique qui s'adapte à n'importe quelle dimension. Je ne comprend pas pourquoi le simple fait de vouloir nommer des coordonnées pour des vecteurs de petite taille laisse à penser que dans le cas où les coordonnées sont anonymes il ne s'agit plus de vecteur.

    En tout cas merci beaucoup pour tes réponses, elles sont vraiment intéressantes et me font beaucoup réfléchir ^^'

  12. #12
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Tu peux peut-être avec une classe générique et activer ou non des fonctions avec std::enable_if.
    J'ai du mal avec ce truc, mais sinon y'a ça avec une fonction intermédiaire (c'est juste plus verbeux):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #include <cstdint>
    #include <iostream>
     
    template<typename TYPE, uint8_t DIMENSION>
    class Vector
    {
        static_assert(DIMENSION > 0, "");
    public:
        TYPE& x() { return mData[0]; }
        const TYPE& x() const { return mData[0]; }
     
        TYPE& y() { return y_internal<DIMENSION >= 2>(); }
     
    private:
        template<bool B>
        TYPE& y_internal();
        template<>
        TYPE& y_internal<true>() { return mData[1]; }
     
    private:
        TYPE mData[DIMENSION];
    };
     
    int main()
    {
        Vector<uint8_t, 1> vec1;
        std::cout << vec1.x() << std::endl;
        Vector<uint8_t, 2> vec2;
        std::cout << vec2.x() << "," << vec2.y() << std::endl;
     
        return 0;
    }
    Si tu essayes d'utiliser y() avec vec1, tu auras une erreur de compil.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  13. #13
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    Par curiosité, ça donnerait quoi avec std::enable_if ?

  14. #14
    Invité
    Invité(e)
    Par défaut
    Bonjour,

    Citation Envoyé par Bousk Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
        template<>
        TYPE& y_internal<true>() { return mData[1]; }
    Tu ne peux pas spécialiser ta fonction template ainsi sans avoir aussi pleinement spécialisé la classe.

    Tu pourrais plus simplement utiliser static_assert() pour générer l'erreur de compilation :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
        TYPE& y() { 
            static_assert(DIMENSION >= 2);
            return mData[1];
        }
    Ou pour une version qui se rapproche de la tienne, à base de tag dispatching :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
        TYPE& y() { return y_internal( std::bool_constant<(DIMENSION >= 2)>() ); }
     
    private:
        TYPE& y_internal(std::false_type) = delete;
        TYPE& y_internal(std::true_type) { return mData[1]; }
    Pour ce qui est de la version std::enable_if, il faut introduire un paramètre template déduit avec la condition testée :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
        template <bool Cond = (DIMENSION >= 2), std::enable_if_t<Cond, int> = 0>
        TYPE& y() { 
            return mData[1];
        }
    std::enable_if peut aussi être placé sur le type de retour ou en paramètre d'entrée avec valeur par défaut. Cf. ce sujet pour plus de détails.

    Bon clairement, static_assert qui donnera en plus un message d'erreur bien plus compréhensible .

  15. #15
    Futur Membre du Club
    Homme Profil pro
    Etudiant
    Inscrit en
    Juillet 2016
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Etudiant

    Informations forums :
    Inscription : Juillet 2016
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Merci à tous !
    Du coup j'ai opté pour un static assert qui fonctionne très bien et correspond exactement à ce qu'il me faut !

    Je met ici le code final (complet) pour les intéréssée

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    template<class T, unsigned int d> class Vector{
    private:
    	T* coord;
    public:
    	constexpr Vector(): coord(new T[d]) {}
    	constexpr Vector(const Vector& v): coord(new T[d]) {
    		for(uint i=0; i<d; ++i)
    			coord[i]=v.coord[i];
    	};
    	template<typename... Ts>
    	constexpr Vector(Ts&&... coords) : coord(new T[d]{std::forward<Ts>(coords)...}){
    		static_assert(sizeof...(Ts) == d, "You must provide d arguments.");
    	}
    	constexpr Vector(Vector&& v){
    		coord = v.coord;
    		v.coord = nullptr;
    	};
    	constexpr Vector(const T &value){
    		coord = new T[d];
    		for(uint i=0; i<d; ++i)
    			coord[i]=value;
    	}
    	inline constexpr T& operator[](uint i){
    		return coord[i];
    	}
    	inline constexpr const T& operator[](uint i) const{
    		return coord[i];
    	}
    	constexpr bool operator==(const Vector<T,d>& vector) const{
    		for(uint i=0; i<d; ++i){
    			if(coord[i]!=vector.coord[i])
    				return false;
    		}
    		return true;
    	}
    	constexpr bool operator!=(const Vector<T,d>& vector) const{
    		for(uint i=0; i<d; ++i){
    			if(coord[i]!=vector.coord[i])
    				return true;
    		}
    		return false;
    	}
    	constexpr Vector<T,d> operator+(const Vector<T,d>& vector) const{
    		Vector<T,d> v=Vector<T,d>();
    		for(uint i=0; i<d; ++i){
    			v.coord[i]=coord[i]+vector.coord[i];
    		}
    		return v;
    	}
    	constexpr Vector<T,d> operator-(const Vector<T,d>& vector) const{
    		Vector<T,d> v=Vector<T,d>();
    		for(uint i=0; i<d; ++i){
    			v.coord[i]=coord[i]-vector.coord[i];
    		}
    		return v;
    	}
    	constexpr Vector<T,d> operator/(const T& t) const{
    		Vector<T,d> v=Vector<T,d>();
    		for(uint i=0; i<d; ++i){
    			v.coord[i]=coord[i]/t;
    		}
    		return v;
    	}
    	constexpr Vector<T,d> operator*(const T& t) const{
    		Vector<T,d> v;
    		for(uint i=0; i<d; ++i){
    			v.coord[i]=coord[i]*t;
    		}
    		return v;
    	}
    	constexpr auto operator*=(const T& t) &{
    		for(uint i=0; i<d; ++i){
    			coord[i]*=t;
    		}
    		return this;
    	}
    	constexpr auto operator/=(const T& t) &{
    		for(uint i=0; i<d; ++i){
    			coord[i]/=t;
    		}
    		return this;
    	}
    	constexpr auto operator+=(const Vector<T,d>& vector)&{
    		for(uint i=0; i<d; ++i){
    			coord[i]+=vector.coord[i];
    		}
    		return this;
    	}
    	constexpr auto operator-=(const Vector<T,d>& vector)&{
    		for(uint i=0; i<d; ++i){
    			coord[i]-=vector.coord[i];
    		}
    		return this;
    	}
    	constexpr Vector<T,d>& operator=(const Vector<T,d>& vector){
    		if(this!=&vector)
    			for(uint i=0; i<d; ++i){
    				coord[i]=vector.coord[i];
    			}
    		return *this;
    	}
    	~Vector(){
    		delete[] coord;
    	};
    	// Handlers
    	inline constexpr T& x(){
    		static_assert(d==2||d==3, "This handler is undefined for this dimension.");
    		return coord[0];
    	}
    	inline constexpr const T& x() const{
    		static_assert(d==2||d==3, "This handler is undefined for this dimension.");
    		return coord[0];
    	}
    	inline constexpr T& y(){
    		static_assert(d==2||d==3, "This handler is undefined for this dimension.");
    		return coord[1];
    	}
    	inline constexpr const T& y() const{
    		static_assert(d==2||d==3, "This handler is undefined for this dimension.");
    		return coord[1];
    	}
    	inline constexpr T& z(){
    		static_assert(d==3, "This handler is undefined for this dimension.");
    		return coord[2];
    	}
    	inline constexpr const T& z() const{
    		static_assert(d==3, "This handler is undefined for this dimension.");
    		return coord[2];
    	}
    };

  16. #16
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Hellinbox Voir le message
    Et un dernier point: De toute façon, peu importe l'utilisation que en sera faite derrière,
    Justement, non!

    Un type de donnée définit deux choses qui ne peuvent être évaluées qu'ensemble (trois, si on considère l'aspect hardware : l'espace mémoire nécessaire pour la représentation de la donnée dans un système donné):
    1. les manipulations que l'on peut entreprendre à partir d'une donnée particulière et
    2. les règles spécifiques que toute donnée d'un type particulier devra respecter (invariants, pré condition et post conditions d'utilisation)


    Prenons un exemple concrêt : la différence entre le type int et le type unsigned int:

    Ce sont deux types qui représentent des valeurs entières et qui nous autorisent à ce titre un certains nombres de manipulation telles que:
    • l'affectation d'une valeur à une variable
    • les opérations mathématiques classiques (addition, soustraction, multiplication, division et modulo)
    • la comparaison de deux valeurs

    Ce sont deux types de données "suffisamment proches" pour que l'on puisse, dans une certaine mesure du moins, envisager de convertir une donnée d'un type en l'autre type.

    A priori, nous pourrions même dire qu'ils utilisent exactement le même nombre de bits (sur une architecture donnée) pour permettre la représentation des différentes valeurs

    Il n'y a qu'une toute petite différence, un tout petit invariant qui vient tout chambouler et qui nous interdit d'utiliser l'un à la place de l'autre sans effectuer de conversion : le type unsigned int est totalement incapable de représenter de valeur négative.

    Et les conséquence de cette différence "si minime" qu'on aurait vite fait de la considérer comme un "détail indigne de notre intérêt" sont tellement importantes que l'on en est arrivé à programmer les compilateurs de manière à ce qu'ils interdisent toute tentative visant à mélanger les deux types (ou, à tout le moins, de manière à ce qu'ils émettent un avertissement bien senti quand on essaye de le faire).

    Si tu prend le temps de réfléchir sereinement aux différences qui peuvent exister entre un tableau et un vecteur mathématique, tu te rendras compte qu'elles sont largement plus importantes que celles qui séparent le type int du type unsigned int.

    Le piège, c'est que le compilateur n'accorde aucun sens particulier aux identifiants que tu donne aux différents types que tu crées: pour lui, que tu appelle ta classe Vector ou Truc n'aura aucune espèce d'importance.

    Cela implique que, quand tu impose des règles quant à l'utilisation qui peut être faite d'un des types que tu crées toi-même, il n'a pas d'autre choix que de ... te faire une confiance aveugle quant à la validité de ces règles

    Autrement dit, si tu décide qu'une classe Voiture doit hériter de la classe Arbre, il ne pourra dire qu'une seule chose : "bien chef", parce qu'il n'aura aucun moyen de se rendre compte que les notions d'arbres et de voitures sont à ce point différentes qu'il est impossible de trouver le moindre point commun entre les deux.

    Le seul moyen d'éviter ce piège, c'est de se montrer un peu moins bête que l'ordinateur et de faire preuve d'un peu de réflexion. C'est de poursuivre la réflexion jusqu'au point où l'on se rend compte qu'il n'y a -- décidément -- aucun moyen de faire passer une voiture pour un arbre. C'est de se rendre compte qu'il n'y a aucun moyen d'utiliser un tableau de la même manière que celle dont nous prévoyons d'utiliser un vecteur mathématique.

    je voudrais simplement faire une classe vecteur générique qui s'adapte à n'importe quelle dimension.
    Non, tu souhaite créer une classe vecteur générique qui puisse contenir n'importe quel nombre d'élément dont la taille serait définie à la compilation.

    Tu vas me dire que je chipote sur les termes, mais le gros manque de compréhension dans cette discussion (du moins, dans ma partie de la discussion dirons nous ) vient, justement, du choix des termes inapproprié. Parce que cette différence te permet de définir ton deuxième besoin qui est de créer une notion de vecteur mathématique susceptible de fonctionner avec n'importe quel nombre de dimensions.

    Et, grâce à cette différence, tu devrais en arriver à la seule conclusion logique qui est : tu es en présence de deux besoins bien spécifiques. Ce qui ne te laisserait plus d'autre choix que de respecter cette fameuse règle du un nom (groupe nominal) == un type de donnée.

    Par la suite, tu en viendras sans doute à réfléchir aux relations qui existent entre ces deux besoins. Et comme cette relation (si elle existe) doit être conceptuellement correcte, si tu envisages l'héritage (qui est la relation la plus forte qui puisse exister entre deux classes), tu devras confronter cette possibilité au LSP.

    Or le LSP te démontrera que tu ne peux pas faire hériter la notion de vecteur mathématique de la notion de tableau, parce qu'un vecteur mathématique n'est pas -- à proprement parler -- un tableau.

    Et, avant même de confronter ton intention au LSP, tu serais sans doute bien inspiré de réfléchir à la sémantique que tu veux donner à ta notion de vecteur mathématique : de toute évidence, un vecteur mathématique a sémantique de valeur, car
    • on peut le copier
    • on peut assigner la valeur de l'un à un autre
    • il est globalement constant, dans le sens où la plus petite modification d'une des valeurs qui le composent aura pour résultat l'obtention d'un vecteur totalement différent (un vecteur (2,3) et totalement différent du vecteur (2,4) )

    Mais, du coups, cela signifie que ta notion de vecteur mathématique est "peu encline à intervenir dans une relation d'héritage", ce qui représente un sérieux frein opposé à ton intention qui serait -- justement -- de faire hériter cette notion de ta notion de tableau, tu ne trouves pas

    Nous pourrions inverser la réflexion, et se demander si la sémantique d'entité, inhérente aux hiérarchies de classes créées du fait de l'héritage, est adaptée au notion de tableaux et de vecteurs mathématiques que l'on souhaite mettre en oeuvre.

    La sémantique d'entité, donc, va rendre nos classes
    • non copiables
    • non assignables
    • globalement mutables, car ce n'est pas parce que tu modifies une des données qui composent ta classe que tu te retrouve avec une instance différente de celle-ci

    Et BAM: on se rend compte que les deux premiers point invoqués (non copiable et non assignable) vont -- très clairement -- à l'encontre de nos intérêts dans le cadre de notions telles que les tableaux et les vecteur mathématique.

    Un tout petit peu de réflexion nous a donc permis de nous rendre compte au travers de différents aspects que l'héritage ne peut -- décidément -- pas être envisagé pour représenter la relation qui existe entre les deux notions que nous voulons mettre en oeuvre.

    Or, nous sommes toujours bloqués sur un fait majeur : deux notions différentes devront être représentées par deux types différents dans notre code.

    Tu peux toujours décider de prendre quelques libertés face à toute cette réflexion, mais sache que, à termes, la "facilité" dont tu espérerais sans doute profiter en décidant de les prendre finira toujours par se retourner contre toi; parce que tu essayeras d'appliquer des opération mathématiques adaptée à tes vecteurs sur des tableaux ou parce que tu essayeras d'appliquer des modifications adaptées à tes tableaux à des vecteurs, et que cela foutra forcément un bordel incroyable dans tes données.

    Je ne comprend pas pourquoi le simple fait de vouloir nommer des coordonnées pour des vecteurs de petite taille laisse à penser que dans le cas où les coordonnées sont anonymes il ne s'agit plus de vecteur.
    Ce n'est pas le fait de vouloir nommer les données des tableau de petite taille qui pose problème. C'est le fait que tu vas manipuler tes tableaux de manière totalement différente de tes vecteurs, et que si tu ne met pas en place une frontière stricte entre les deux, tu en arriveras à des situations dans lesquelles tu manipulera tes tableau comme des vecteurs et inversement.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  17. #17
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par Hellinbox Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    constexpr auto operator*=(const T& t) &{
    Attention, cette syntaxe ne fait pas du tout ce que tu crois :

    Cet auto avec return this; va déduire le type Vector<T,d> * et retourner le pointeur.
    Et ce & placé à la fin signifie que cet opérateur ne pourra s'appliquer que sur une lvalue.

    La syntaxe correcte impliquant auto aurait été
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    constexpr auto operator*=(const T& t) -> Vector<T,d> &{
        ...
        return *this;
    }
    Il te manque aussi l'opérateur d'affectation par déplacement. Remercie ton pointeur nu qui t'oblige à le recoder ainsi que ses copains. Autant l'éviter .
    Dernière modification par Invité ; 26/07/2019 à 17h20.

  18. #18
    Futur Membre du Club
    Homme Profil pro
    Etudiant
    Inscrit en
    Juillet 2016
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Etudiant

    Informations forums :
    Inscription : Juillet 2016
    Messages : 11
    Points : 5
    Points
    5
    Par défaut
    Citation Envoyé par Winjerome
    Attention, cette syntaxe ne fait pas du tout ce que tu crois :
    Merci je vais corriger ça !
    Citation Envoyé par Winjerome
    Il te manque aussi l'opérateur d'affectation par déplacement. Remercie ton pointeur nu qui t'oblige à le recoder ainsi que ses copains. Autant l'éviter .
    Tu préconiserais plutôt un tableau ? Dans ce cas, est-ce que ce ne serai pas moins intéressant car il faut ré-allouer un tableau alors qu'avec un pointeur il suffit d'échanger les adresses non ? À moins que le compilateur sache optimiser ça !
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    constexpr Vector(Vector&& vector) : coord(nullptr) {
    	std::swap(coord, vector.coord);
    };
    constexpr Vector<T,d>& operator=(Vector<T,d> vector){
    	std::swap(coord, vector.coord);
    	return *this;
    }
    Je me suis servie de cette ressource là pour l'écrire: https://cpp.developpez.com/faq/cpp/?...tation-correct

    Citation Envoyé par koala01
    Non, tu souhaite créer une classe vecteur générique qui puisse contenir n'importe quel nombre d'élément dont la taille serait définie à la compilation.
    Oui c'est plutôt ça ^^'
    Je n'avais pas bien défini ce que je voulais en faire exactement, (ce qui me fait penser que mes exemples n'était pas très pertinents...) j'avais simplement en tête de coder un truc que je puisse réutiliser dans un cadre plus large comme un logiciel de calcul formel mais sans idée vraiment précise.
    Enfin bref, je crois que j'ai compris, merci beaucoup !


+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 9
    Dernier message: 05/01/2010, 08h32
  2. Spécialisation partielle d'un membre de classe
    Par Invité dans le forum Langage
    Réponses: 4
    Dernier message: 11/06/2009, 14h43
  3. Réponses: 4
    Dernier message: 15/10/2008, 09h33
  4. definition partielle de classe
    Par SamAgace dans le forum C++
    Réponses: 5
    Dernier message: 26/09/2007, 18h06
  5. Réponses: 4
    Dernier message: 29/01/2006, 17h54

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo