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 :

Problème de classes template + CRTP


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé
    Homme Profil pro
    Doctorant en Astrophysique
    Inscrit en
    Mars 2009
    Messages
    312
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Astrophysique
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2009
    Messages : 312
    Par défaut Problème de classes template + CRTP
    Bonjour à tous.

    Je suis en train d'implémenter une bibliothèque d'algèbre linéaire pour des petits vecteurs et matrices + des fonctions répondant à mon domaine de recherche et j'ai un petit problème. Je connaissais vaguement le CRTP, et après quelques tests, il s'avère que c'est bien adapté à ce que je veux faire. J'ai donc fait un petit programme de test, mais il reste un souci.

    Voici d'abord le programme de test (le pb est à l'avant dernière ligne) :
    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
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    #include <iostream>
    #include <initializer_list>
    #include <type_traits>
     
    // Abstract class
    template<class TCRTP, class T, unsigned int TSIZE> class AbstractArray
    {
        // Constructor
        public:
            inline AbstractArray() : _data{}
            {
                std::cout<<"AbstractArray::AbstractArray()"<<std::endl;
            }
     
        // Copy constructor
        public:
            template<class TCRTP0, class T0> inline AbstractArray(const AbstractArray<TCRTP0, T0, TSIZE> &rhs)
            {
                std::cout<<"AbstractArray::AbstractArray(const AbstractArray<TCRTP0, T0, TSIZE> &rhs)"<<std::endl;
                for(unsigned int i = 0; i < TSIZE; ++i) {
                    _data[i] = rhs[i];
                }
            }
     
        // Initializer list constructor
        public:
            template<class T0> inline AbstractArray(const std::initializer_list<T0>& rhs)
            {
                std::cout<<"AbstractArray::AbstractArray(const std::initializer_list<T0>& rhs)"<<std::endl;
                const T0* it = rhs.begin();
                for (unsigned int i = 0; i < TSIZE; ++i) {
                    _data[i] = *it;
                    ++it;
                }
            }
     
        // Destructor
        public:
            inline ~AbstractArray()
            {
                std::cout<<"AbstractArray::~AbstractArray()"<<std::endl;
            }
     
        // Subscript operator
        public:
            inline const T& operator[](const unsigned int i) const
            {
                std::cout<<"AbstractArray::operator[](const unsigned int i) const"<<std::endl;
                return _data[i];
            }
            inline T& operator[](const unsigned int i)
            {
                std::cout<<"AbstractArray::operator[](const unsigned int i)"<<std::endl;
                return _data[i];
            }
     
        // Assignment operator
        public:
            template<class TCRTP0, class T0> inline AbstractArray<TCRTP, T, TSIZE>& operator=(const AbstractArray<TCRTP0, T0, TSIZE>& rhs)
            {
                std::cout<<"AbstractArray::operator=(const AbstractArray<TCRTP0, T0, TSIZE>& rhs)"<<std::endl;
                for (unsigned int i = 0; i < TSIZE; ++i) {
                    _data[i] = rhs[i];
                }
                return *this;
            }
     
        // Sum assignment
        public:
            template<class TCRTP0, class T0> inline AbstractArray<TCRTP, T, TSIZE>& operator+=(const AbstractArray<TCRTP0, T0, TSIZE>& rhs)
            {
                std::cout<<"AbstractArray::operator+=(const AbstractArray<TCRTP0, T0, TSIZE>& rhs)"<<std::endl;
                for (unsigned int i = 0; i < TSIZE; ++i) {
                    _data[i] += rhs[i];
                }
                return *this;
            }
     
        // Sum operator
        public:
            template<class T0> inline AbstractArray<TCRTP, typename std::common_type<T, T0>::type, TSIZE> operator+(const AbstractArray<TCRTP, T0, TSIZE>& rhs) const
            {
                return AbstractArray<TCRTP, typename std::common_type<T, T0>::type, TSIZE>(*this) += rhs;
            }
     
        // Data members
        protected:
            T _data[TSIZE];
    };
     
    // Array class
    template<class T, unsigned int TSIZE> class NArray : public  AbstractArray<NArray<T, TSIZE>, T, TSIZE>
    {
        // Constructor
        public:
            inline NArray() : AbstractArray<NArray<T, TSIZE>, T, TSIZE>()
            {
                std::cout<<"NArray::NArray()"<<std::endl;
            }
     
        // Copy constructor
        public:
            template<class TCRTP0, class T0> inline NArray(const AbstractArray<TCRTP0, T0, TSIZE> &rhs) : AbstractArray<NArray<T, TSIZE>, T, TSIZE>(rhs)
            {
                std::cout<<"NArray::NArray(const AbstractArray<TCRTP0, T0, TSIZE> &rhs)"<<std::endl;
            }
     
        // Initializer list constructor
        public:
            template<class T0> inline NArray(const std::initializer_list<T0>& rhs) : AbstractArray<NArray<T, TSIZE>, T, TSIZE>(rhs)
            {
                std::cout<<"NArray::NArray(const std::initializer_list<T0>& rhs)"<<std::endl;
            }
     
        // Destructor
        public:
            inline ~NArray()
            {
                std::cout<<"NArray::~NArray()"<<std::endl;
            }
    };
     
    // Vector class
    template<class T, unsigned int TSIZE> class NVector : public  AbstractArray<NVector<T, TSIZE>, T, TSIZE>
    {
        // Constructor
        public:
            inline NVector() : AbstractArray<NVector<T, TSIZE>, T, TSIZE>()
            {
                std::cout<<"NVector::NVector()"<<std::endl;
            }
     
        // Copy constructor
        public:
            template<class TCRTP0, class T0> inline NVector(const AbstractArray<TCRTP0, T0, TSIZE> &rhs) : AbstractArray<NVector<T, TSIZE>, T, TSIZE>(rhs)
            {
                std::cout<<"NVector::NVector(const AbstractArray<TCRTP0, T0, TSIZE> &rhs)"<<std::endl;
            }
     
        // Initializer list constructor
        public:
            template<class T0> inline NVector(const std::initializer_list<T0>& rhs) : AbstractArray<NVector<T, TSIZE>, T, TSIZE>(rhs)
            {
                std::cout<<"NVector::NVector(const std::initializer_list<T0>& rhs)"<<std::endl;
            }
     
        // Destructor
        public:
            inline ~NVector()
            {
                std::cout<<"NVector::~NVector()"<<std::endl;
            }
    };
     
    // Main
    int main()
    {
        NArray<double, 3> a1({1., 2., 3.});
        std::cout<<std::endl;
        NArray<int, 3> a2({4., 5., 6.});
        std::cout<<std::endl;
        NArray<double, 3> a3({7., 8., 9.});
        std::cout<<std::endl;
        NVector<double, 3> v1({11., 12., 13.});
        std::cout<<std::endl;
        NVector<double, 3> v2({14., 15., 16.});
        std::cout<<std::endl;
        NVector<double, 3> v3({17., 18., 19.});
        std::cout<<std::endl;
        NVector<int, 3> v4({20., 21., 22.});
        std::cout<<std::endl;
        a1 = a2;
        std::cout<<std::endl;
        std::cout<<"TEST -> a1 = "<<a1[0]<<" "<<a1[1]<<" "<<a1[2]<<std::endl;
        std::cout<<std::endl;
        v1 = a2;
        std::cout<<std::endl;
        std::cout<<"TEST -> v1 = "<<v1[0]<<" "<<v1[1]<<" "<<v1[2]<<std::endl;
        std::cout<<std::endl;
        v1 += a2;
        std::cout<<std::endl;
        std::cout<<"TEST -> v1 = "<<v1[0]<<" "<<v1[1]<<" "<<v1[2]<<std::endl;
        std::cout<<std::endl;
        v1 = a3+a3;
        std::cout<<std::endl;
        std::cout<<"TEST -> v1 = "<<v1[0]<<" "<<v1[1]<<" "<<v1[2]<<std::endl;
        std::cout<<std::endl;
        //v2 = v3+v4; // <- This line does not work : "error : no match for "operator+" in "v3+v4"
        std::cout<<std::endl;
        return 0;
    }
    Bref tout fonctionne à part quand j'appelle v2 = v3+v4 où v2 est un vecteur de double, v3 est un vecteur de double et v4 est un vecteur d'integer. (la somme de la ligne précédente v1 = a3 + a3, elle fonctionne, où tout est du même type).

    Auriez vous une idée pour corriger le problème ?

    D'autre part, si vous avez des idées pour améliorer l'efficacité ou la qualité du code (ou si vous constatez des trucs "dangereux" dans le code qui précède), avant que je me lance dans la conversion de l'implémentation actuelle à une implémentation basée sur cet exemple, vos conseils seront grandement appréciés (par contre, je ne vais pas me lancer dans de la "vraie" métaprogrammation car le loop unrolling du compilo connaissant la taille fixe des tableaux me suffit bien).

    Merci infiniment

  2. #2
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Pour la qualité du code, il y a plusieurs choses à voir:

    "public:" est un marqueur qui se propage, il n'est pas attaché à une instruction particulière. Il est inutile de le répéter devant chaque fonction (ca sent le javaiste)

    inline n'est pas utile dans la définition d'une classe, mais requis partout ailleurs dans un en-tête.

    utilise quelques typedef, éventuellement privés, dans les classes
    par exemple parent_t et self_t
    exemple avec NArray:
    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
    // Array class
    template<class T, unsigned int TSIZE>
    class NArray : public  AbstractArray<NArray<T, TSIZE>, T, TSIZE> {
    	private:
    		typedef AbstractArray<NArray<T, TSIZE>, T, TSIZE> parent_t;
    	public:
    		NArray() : parent_t() {}
     
    		template<class TCRTP0, class T0>
    		NArray(const parent_t &rhs) : parent_t(rhs) {}
     
    		template<class T0>
    		NArray(const std::initializer_list<T0>& rhs) :AbstractArray<NArray<T, TSIZE>, T, TSIZE>(rhs) {}
     
    		// Destructor
    		~NArray() {}
    };
    On ne dois jamais utiliser d'identificateur commencant par un '_', ils sont réservés pour les compilateurs.
    Dans AbstractArray, j'utiliserai l'un des noms suivants:
    • data
    • m_data
    • mData
    • data_m
    avec une préférence pour data.

    Si tu veux une taille fixe, tu peux regarder du coté de <array>, avec std::array<typename, int>

    Il n'est pas toujours requis de rappeler les parametres templates d'une fonction membre.
    par exemple, ceci suffirait dans AbstractArray:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template<class TCRTP0, class T0>
    AbstractArray& operator+=(const AbstractArray<TCRTP0, T0, TSIZE>& rhs);

  3. #3
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 152
    Billets dans le blog
    4
    Par défaut
    Bonjour,

    est-ce que v2 += v4 fonctionne ?
    Si oui, alors c'est juste une erreur d'innatention je pense :
    Ton opérateur += est déclaré comme ceci
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    template<class TCRTP0, class T0> inline AbstractArray<TCRTP, T, TSIZE>& operator+=(const AbstractArray<TCRTP0, T0, TSIZE>& rhs)
    Tandis que ton opérateur + est déclaré comme ceci
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    template<class T0> inline AbstractArray<TCRTP, typename std::common_type<T, T0>::type, TSIZE> operator+(const AbstractArray<TCRTP, T0, TSIZE>& rhs) const
    Ce qui t'interdit de sommer un Abstract<double> avec un Abstract<int>, tu ne peux sommer que Abstract<T> avec un autre Abstract<T>.

    En tous cas, les opérateurs + et += devraient avoir une signature quasi similaire, donc ça me surprend.
    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.

  4. #4
    Invité
    Invité(e)
    Par défaut
    On ne dois jamais utiliser d'identificateur commencant par un '_', ils sont réservés pour les compilateurs.
    pas exactement.
    _estOk
    tant qu'apres l'underscore est une minuscule et que c'est dans un namespace style la classe, c'est bon.

  5. #5
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Par défaut
    @galerien69
    En effet, c'est pas interdit. Par contre, on recommande souvent de ne pas utiliser de mot commençant par _ pour éviter les erreurs. Mais c'est plus une question de style et de sécurité

    @Kaluza
    C'est effectivement un problème avec l'absence de paramètre template TCRTP0 comme l'a dit Bousk

    Regarde en détail la signature de la fonction cherchée par le compilateur. Pour évaluer v4 + v3, le compilateur cherche (v4)::operator+
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    v4 -> 
        NV<double, 3> : AA< NV<double, 3>, double, 3>
    
    v4::operator+   -> 
        template<T0>
        NV<double, 3> : AA< NV<double, 3>, double, 3> :: 
        operator+ ( AA< TCRTP = NV<double, 3>, T0, TSIZE =3> )
    
    v3 -> 
        NV<int, 3> : AA< NV<int, 3>, int, 3>
    Il n'y a pas de type T0 pouvant correspondre pour v3 -> le compilateur ne trouve pas

    En ajouter TCRTP en paramètre template, ça fonctionne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template<class TCRTP0, class T0>
    inline AbstractArray<TCRTP0, typename std::common_type<T, T0>::type, TSIZE>
    operator+ (const AbstractArray<TCRTP0, T0, TSIZE>& rhs) const
    {
        return AbstractArray<TCRTP, typename std::common_type<T, T0>::type, TSIZE> (*this) += rhs;
    }

    Par contre, il peut y avoir très clairement un problème avec common_type, puisque que le type retourné peut ne pas correspondre à T dans le TCRP...

    En fait, en terme de design, je trouve pas ça top d'avoir le type manipulé en interne en double dans les paramètres template (dans TCRTP et dans T).
    Je te conseille de garder que le paramètre TCRTP et de récupérer T et SIZE comme membre de TCRTP :
    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
    template<class TCRTP> 
    class AbstractArray {
        (...)
    public:
        template<class TCRTP0>
        inline AbstractArray<
                typename std::common_type<
                    typename TCRTP::internal_type, 
                    typename TCRTP0::internal_type
                >::type
            >
        operator+ (const AbstractArray<TCRTP0>& rhs) const
        {
            typedef typename TCRTP::internal_type lhs_type;
            typedef typename TCRTP0::internal_type rhs_type;
            typedef typename std::common_type<lhs_type, rhs_type>::type internal_return_type;
            typedef AbstractArray<internal_return_type> return_type;
     
            return static_cast<return_type>((*this) += rhs); // moche ça
        }
    };
     
    template<class T, unsigned int TSIZE>
    class NVector : public  AbstractArray<NVector<T, TSIZE>>
    {
        typedef T internal_type;
        typedef SIZE internal_size;
    (...)
    };

    par contre, je ne vais pas me lancer dans de la "vraie" métaprogrammation car le loop unrolling du compilo connaissant la taille fixe des tableaux me suffit bien
    Cette phrase me fait tiquer. En général, on perd du temps à réimplémenter pour des besoins spécifique (souvent, pour des raisons de performances ou parce que le compilateur ne gère pour des libs numériques récentes). Là clairement tu ne cherches pas la performance si tu te contentes du déroulage des boucles et tu utilises un compilateur récent
    Pourquoi refais tu tout ça du coup ?

  6. #6
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    J'ai une autre suggestion.

    Tu veux constituer une librairie de manipulation de vecteurs et matrices.

    Pour les vecteurs, pourquoi ne pas utiliser std::array<T, N>, et définir les operateurs mathématiques comme fonctions libres.

    De même pour les matrices, se seront des std::array<std::array<T, N>, M>.

    Je ne garantis pas que ce soit le résultat attendu, cependant.

    PS: Je vais personnellement essayer, parce que ce type de librairie me sera utile. Merci pour m'avoir donné ce projet

  7. #7
    Membre éclairé
    Homme Profil pro
    Doctorant en Astrophysique
    Inscrit en
    Mars 2009
    Messages
    312
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Astrophysique
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2009
    Messages : 312
    Par défaut
    Merci pour votre aide, tout cela est très instructif, j'avais effectivement oublié un typename.

    @ gbdivers
    En fait, en terme de design, je trouve pas ça top d'avoir le type manipulé en interne en double dans les paramètres template (dans TCRTP et dans T).
    Je te conseille de garder que le paramètre TCRTP et de récupérer T et SIZE comme membre de TCRTP :
    Oui effectivement ta méthode est meilleure. Je me demandais si pour passer les types, une classe de traits n'était pas adaptée.
    Et d'autre part par quoi tu remplacerais ce que tu as noté "moche ça" dans ton code ?

    Cette phrase me fait tiquer. En général, on perd du temps à réimplémenter pour des besoins spécifique (souvent, pour des raisons de performances ou parce que le compilateur ne gère pour des libs numériques récentes). Là clairement tu ne cherches pas la performance si tu te contentes du déroulage des boucles et tu utilises un compilateur récent
    Pourquoi refais tu tout ça du coup ?
    En fait si, je recherche la performance, mais disons pour un temps de codage "raisonnable". Et dans tous les benchs que j'ai pu faire, le fait est que dans la pratique, avec du CRTP et des tableaux C "fixes" tel qu'ici j'obtiens pratiquement les même perfs que si je faisais des opérations sur des variables qui n'étaient pas dans des tableaux. A titre de comparaison les std::valarray sont environ 8 fois plus lents. Je ne sais pas trop pour quoi, mais disons que cette méthode fonctionne très bien sans même passer par de l'expression template bourrin.

    Le but n'est pas du tout de faire quelque chose à la manière de BLAS, mais de pouvoir travailler efficacement avec de petits tableaux fixes : des vecteurs, des tenseurs ou des connexions affines. Comme je n'ai rien trouvé qui me satisfait pleinement, je recode tout from scratch, comme ça en plus je peux "tuner" à la main certains truc dont j'ai besoin. Au final c'est pour faire de la relativité générale.

Discussions similaires

  1. Problème de classe "template"
    Par Josiane22 dans le forum C++
    Réponses: 3
    Dernier message: 27/03/2013, 23h04
  2. problème avec classe template
    Par arthurembo dans le forum Langage
    Réponses: 2
    Dernier message: 09/02/2009, 07h46
  3. Problème déclaration classe template
    Par olivier1978 dans le forum Langage
    Réponses: 3
    Dernier message: 21/11/2007, 23h43
  4. Problème de class template et std::map
    Par bathof dans le forum Langage
    Réponses: 2
    Dernier message: 31/07/2007, 22h18
  5. [DLL/classe template] problème de link
    Par Bob.Killer dans le forum C++
    Réponses: 7
    Dernier message: 31/08/2005, 18h56

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