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 :

CRTP et operator=


Sujet :

Langage C++

  1. #1
    Membre habitué
    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
    Points : 176
    Points
    176
    Par défaut CRTP et operator=
    Bonjour à tous.

    J'ai un petit problème. Alors que l'operator= est censé se transmettre par héritage, l'exemple suivant ne compile pas et je ne sais pas pourquoi (erreur de compilation à 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
    #include <iostream>
    #include <type_traits>
     
    // Base class
    template<template<typename, unsigned int> class CRTP, typename T, unsigned int N> class Base
    {
        // Cast to base
        public:
            inline Base<CRTP, T, N>& operator()()
            {
                return *this;
            }
     
        // Operator =
        public:
            template<typename T0, class = typename std::enable_if<std::is_convertible<T0, T>::value>::type>
            inline CRTP<T, N>& operator=(const T0& rhs)
            {
                for (unsigned int i = 0; i < N; ++i) {
                    _data[i] = rhs;
                }
                return static_cast<CRTP<T, N>&>(*this);
            }
     
        // Data members
        protected:
            T _data[N];
    };
     
    // Derived class
    template<typename T, unsigned int N> class Derived : public Base<Derived, T, N>
    {
    };
     
    // Main
    int main()
    {
        Derived<double, 3> x;
        x() = 3; // <- This is OK
        x = 3;   // <- error: no match for 'operator=' in ' x=3 '
        return 0;
    }
    Pourquoi cela ne compile-t-il pas ?
    Quelle est la façon la plus simple de résoudre le problème ? (si possible sans rajouter de classe annexe)

    Merci beaucoup

  2. #2
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Citation Envoyé par Kaluza Voir le message
    Bonjour à tous.

    J'ai un petit problème. Alors que l'operator= est censé se transmettre par héritage, l'exemple suivant ne compile pas et je ne sais pas pourquoi (erreur de compilation à l'avant dernière ligne) :
    Rien n'est transmis pas héritage. De ce point de vue, operator= n'est pas différent d'une autre fonction, le lookup fait la recherche en partant du type statique et remonte.

    Citation Envoyé par Kaluza Voir le message
    Pourquoi cela ne compile-t-il pas ?
    Quelle est la façon la plus simple de résoudre le problème ? (si possible sans rajouter de classe annexe)

    Merci beaucoup
    Ca ne compile pas car, là c'est une particularité de operator= (et quelques autres fonctions), si l'utilisateur n'est déclare pas, un operator= est déclaré automatique, ainsi il masque l'operator= de ta classe mère.

    La solution est de réinjecter ton operator= dans ta classe :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    using Derived::Base::operator=;
    Edit: Code complet testé sous gcc 4.6 :
    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<cstddef>
    #include<type_traits>
     
    template<template<class,std::size_t> class CRTP, class T, std::size_t N>
    struct Base
    {
      template<class T0, class = typename std::enable_if<std::is_convertible<T0, T>::value>::type>
      CRTP<T, N>& operator=(T0 const& rhs)
      {
        for(auto& _d : _data)
          _d = rhs;
     
        return static_cast<CRTP<T, N>&>(*this);
      }
     
    protected:
      Base(){}
      ~Base(){}
     
      T _data[N];
    };
     
    template<typename T, unsigned int N>
    struct Derived : Base<Derived, T, N>
    { using Derived::Base::operator=; };
     
    int main()
    {
      Derived<double, 3> x;
      x = 3;
    }

  3. #3
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    CRTP ou pas, l'héritage public reste une relation de substitution, plus appropriée pour des sémantiques d'entités et en générale incompatibles avec des surcharges d'opérateurs.
    cf Quand est-ce qu'une classe a une sémantique de d'entité ?
    Pourquoi mettre en œuvre un héritage ?

  4. #4
    Membre habitué
    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
    Points : 176
    Points
    176
    Par défaut
    @Flob90 : merci beaucoup, j'ai compris d'où venait le problème et maintenant tout est rentré dans l'ordre

    @3DArchi : je ne comprends pas bien pourquoi l'héritage public est incompatible avec la surcharge d'opérateurs. J'ai lu les liens vers la FAQ, j'ai compris la différence entre sémantique d'entité et sémantique de valeur, mais je ne comprends pas pour quelle raison il est mieux de faire de l'héritage privé pour faire de la surcharge. Pourrais-tu m'éclairer sur ce point (si tu as un exemple de ce pour quoi c'est dangereux/ça ne marche pas en passant par de l'héritage public, je suis preneur). Merci beaucoup

  5. #5
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par Kaluza Voir le message
    @3DArchi : je ne comprends pas bien pourquoi l'héritage public est incompatible avec la surcharge d'opérateurs.
    C'est plutôt que l'héritage publique est lié avec la sémantique d'entité et que la sémantique d'entité se marie mal avec la copie. Donc avec l'opérateur = (et avec le constructeur de copie et avec la sémantique de mouvement). La copie rompt l'invariant par essence de la sémantique d'entité qu'est l'identité, l'unicité (on peut tout de fois considérer le clonage comme la création d'une nouvelle entité avec le même état de départ que l'entité source, mais il s'agit bien d'une nouvelle entité qui ne sera pas substituable dans une expression à l'entité d'origine sans changer la sémantique de l'expression. Par là, le clonage doit se faire avec une interface bien identifiée comme telle).
    Les opérateurs arithmétiques sont quand même assez liés à la notion de valeur (si on veut éviter le principe de moindre surprise) et, ceci dit, nécessitent souvent une copie (c = a + b)...
    Enfin, les opérateurs de comparaison s'appliquent d'abord à des valeurs et peuvent rapidement contredire la notion d'identité : dans un espace ordonné, si je me souviens bien, (x ≤ y && y ≤ x => x == y).
    Ca, c'est pour Platon.


    D'un point de vue pratique, quelque soit l'opérateur binaire tu vas rapidement être embêté sur des expressions a OP b lorsque tu fais varier le type de a et b des classes de bases vers les classes dérivées :
    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
     
    struct base
    {//...
    };
     
    struct derivee : public base
    {//...
    };
     
    int main()
    {
    base a;
    base ax;
    derivee b;
    derivee bx;
       a = ax;// à la rigueur
       b = bx;// à la rigueur
       a = bx;// ????
       b = ax;// ????
    base &ra = a;
    base &rb = b;
    base &rbx = bx;
       ra = rb; // ???? 
       rb = b; // ????
       rb = a; // ????
       rb = rbx;   // ????
    }
    Quelle fonction faut-il appeler ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    base& base::operator=(base const&);
    base& base::operator=(derivee const&);
    base& derivee::operator=(derivee const&);
    base& derivee::operator=(base const&);
    derivee& derivee::operator=(derivee const&);
    derivee& derivee::operator=(base const&);
    Citation Envoyé par Kaluza Voir le message
    J'ai lu les liens vers la FAQ, j'ai compris la différence entre sémantique d'entité et sémantique de valeur,
    Il y a aussi l'article d'Emmanuel Deloget qui, maintenant corrigé, est intéressant : Valeurs et entités : les deux grandes classes d'objets

    Citation Envoyé par Kaluza Voir le message
    mais je ne comprends pas pour quelle raison il est mieux de faire de l'héritage privé pour faire de la surcharge.
    Le principe CRTP est un outils du langage très pratique. Mais le lien d'héritage publique garde son sens. Est ce que derivee EST-UN base<derivee> ? Car si le lien est derivee EST-IMPLEMENTE-EN-TERME-DE base<derivee>, c'est alors un héritage privée qu'il faut adopter.

    Ma remarque visait surtout à souligner que que ce soit le CRTP ou n'importe quel autre outil générique, il ne faut pas jeter aux orties les principes de bases de l'objet et en particulier de la sémantique très forte d'un lien d'héritage public. Un héritage public doit respecter le LSP, s'applique plutôt au sémantique d'entité, s'articule mal sémantiquement et syntaxiquement avec la copie etc...

  6. #6
    Membre habitué
    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
    Points : 176
    Points
    176
    Par défaut
    Ok merci pour c'est déjà beaucoup plus clair pour moi, surtout avec ton exemple pratique . Je n'ai pas une formation d'informaticien (je passé par un parcours de physique fondamentale), et j'ai pratiquement tout appris "en ligne", donc j'imagine que ce genre de choses est beaucoup plus clair pour quelqu'un qui a appris tout le côté "théorique" de la programmation objet.

    J'ai bien vu le problème que cela pouvait poser avec les opérateurs binaires, mais ma classe de Base qui implémente le CRTP étant considérée comme abstraite (destructeur protégé), je n'autorise que des expressions du genre :
    Crtp = Crtp+Crtp
    Si l'utilisateur veut faire des choses du genre
    Crtp = Crtp+Crtp1
    il doit passer par un constructeur explicite et plutôt écrire:
    Crtp = Crtp+Crtp(Crtp1)

  7. #7
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par Kaluza Voir le message
    ma classe de Base qui implémente le CRTP étant considérée comme abstraite (destructeur protégé),
    Juste pour le terme, abstrait = virtuel pur : . Rien à voir avec la portée.


    Citation Envoyé par Kaluza Voir le message
    je n'autorise que des expressions du genre :
    Crtp = Crtp+Crtp
    Si l'utilisateur veut faire des choses du genre
    Crtp = Crtp+Crtp1
    il doit passer par un constructeur explicite et plutôt écrire:
    Crtp = Crtp+Crtp(Crtp1)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int main()
    {
      Derived<double, 3> x;
      Derived<double, 3> y;
      x = y;
     
      Base<Derived,double, 3> &rx = x;
      Base<Derived,double, 3> &ry = y;
      rx = ry; // base- base et non plus CRTP - CRTP
     
      return 0;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    template<typename T, unsigned int N>
    struct Derived2 : Base<Derived, T, N>
    {
    };
    int main()
    {
      Derived<double, 3> x;
      Derived2<double, 3> y;
      Base<Derived,double, 3> &rx = x;
      Base<Derived,double, 3> &ry = y;
      rx = ry; // base/CRTP- base/CRTP2...
     
    return 0;

    Visiblement, tout indique un héritage privé. Autant le faire, non ?
    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
     
    #include<cstddef>
    #include<type_traits>
     
    template<template<class,std::size_t> class CRTP, class T, std::size_t N>
    struct Base
    {
      template<class T0, class = typename std::enable_if<std::is_convertible<T0, T>::value>::type>
      CRTP<T, N>& operator=(T0 const& rhs)
      {
        for(auto& _d : _data)
          _d = rhs;
     
        return static_cast<CRTP<T, N>&>(*this);
      }
     
    protected:
      Base(){}
      ~Base(){}
     
      T _data[N];
    };
     
    template<typename T, unsigned int N>
    struct Derived : private Base<Derived, T, N>
    {
        friend class Base< ::Derived, T, N>;
        using Derived::Base::operator=;
    };
     
    int main()
    {
      Derived<double, 3> x;
      x = 3;
    }

  8. #8
    Membre habitué
    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
    Points : 176
    Points
    176
    Par défaut
    Merci beaucoup pour l'aide que tu m'apportes

    Mais en fait, je ne comprends pas comment l'héritage privé peut fonctionner avec les opérateurs. En passant par l'héritage privé, tous les opérateurs vont devenir des membres privés des classes dérivées non ? Du coup il sera impossible de les appeler de l'extérieur. Genre ça :
    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
    #include <iostream>
     
    class Base
    {
        public:
            Base operator+(const Base& rhs)
            {
                std::cout<<"Base operator+(const Base& rhs)"<<std::endl;
                return Base(*this);
            }
    };
     
    class Derived : private Base
    {
    };
     
    int main()
    {
        Derived x;
        Derived y;
        x+y;
        return 0;
    }
    ça me dit à juste titre : "error: 'Base Base::operator+(const Base&)' is inaccessible"

    Je zappe forcément un point important, mais je ne vois pas lequel.

  9. #9
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    En définissant les opérateurs en fonction libre dans la classe grâce à friend (boost fait quelque chose du genre je ne sais plus où). C'est un peu trick mais bon.

    Sinon, using peut ramener en portée publique.

    Je n'ai pas le temps tout de suite, je poste des exemples plus tard.

  10. #10
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,

    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
    #include <iostream>
    template<class T>
    class Base
    {
        public:
           friend
            T operator+(T lhs, const T& rhs) // ce n'est pas une fonction de Base mais une fonction libre. Ce qui est d'ailleurs souvent plus cohérent avec les opérateurs binaires
            {
                std::cout<<"T operator+(const T& rhs)"<<std::endl;
                return lhs+=rhs;
            }
    };
     
    class Derived : private Base<Derived>
    {
    public:
       Derived& operator+=(Derived const& rhs)
       {
          return *this;
       }
    };
     
    int main()
    {
        Derived x;
        Derived y;
        x+y;
        return 0;
    }
    ou

    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
    #include <iostream>
    template<class T>
    class Base
    {
        public:
            T& operator+=(const T& rhs)
            {
                std::cout<<"T operator+=(const T& rhs)"<<std::endl;
                return static_cast<T&>(*this);
            }
    };
     
    class Derived : private Base<Derived>
    {
        friend class Base<Derived>; // necessaire pour le static_cast dans la classe parent
    public:
        using Base<Derived>::operator+=;
    };
     
    int main()
    {
        Derived x;
        Derived y;
        x+=y;
        return 0;
    }

  11. #11
    Membre habitué
    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
    Points : 176
    Points
    176
    Par défaut
    Ok merci beaucoup pour ces exemples très instructifs . En fait je pensais que je zappais un truc sur l'héritage quand tu parlais de passer les opérateurs via un héritage privé. En fait non, j'étais seulement pas très au fait de repasser les fonctions en publiques via les friend/using.

  12. #12
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    En fait, la seule chose que je voulais dire, c'est que template ou pas, le reste des principes de design doivent continuer à être respecté (LSP si héritage publique, SRP, ISP, OCP, privilégier la pureté, bien nommer ses éléments, éviter les cycles, etc...).

Discussions similaires

  1. [Débutant][String] Opérations sur une chaîne
    Par gandalf_le_blanc dans le forum Général Java
    Réponses: 8
    Dernier message: 08/06/2004, 11h59
  2. afficher la signature des opérations dans XDE
    Par ChristopheH dans le forum Rational
    Réponses: 1
    Dernier message: 24/05/2004, 15h41
  3. [JSP] thread ? Message d'avancement des operations en cours
    Par buffyann dans le forum Servlets/JSP
    Réponses: 14
    Dernier message: 18/12/2003, 11h39
  4. operation sur des alias
    Par 74160 dans le forum Requêtes
    Réponses: 4
    Dernier message: 24/11/2003, 18h19
  5. Réponses: 8
    Dernier message: 21/11/2003, 18h38

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