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++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre habitué
    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
    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
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 493
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 493
    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
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    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

  4. #4
    Membre habitué
    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
    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 !

  5. #5
    Membre habitué
    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
    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.)

  6. #6
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    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

  7. #7
    Membre habitué
    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
    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 .

  8. #8
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    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

+ 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