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 :

Pourquoi ne peut-on pas modifier une rvalue non objet en C++ ?


Sujet :

Langage C++

  1. #1
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut Pourquoi ne peut-on pas modifier une rvalue non objet en C++ ?
    Pourquoi ne peut-on pas modifier une rvalue non objet alors que l'on peut appeler une fonction membre non constante sur une rvalue objet non constante ?
    Notamment, pourquoi n'a-t-on pas le droit de faire une pré-incrémentation sur un pointeur nu qui est une rvalue alors qu'on a le droit d'appeler operator++ sur une rvalue objet non constante ?

    Exemple avec g++ 6.2.0 :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <set>
    #include <array>
     
    int main() {
        std::set<int>      set = {0, 1, 2, 3};
        std::array<int, 4> tab = {0, 1, 2, 3};
        auto setIt = ++set.begin(); // Compile, car std::set<int>::iterator est un objet.
        auto tabIt = ++tab.begin(); // Ne compile pas, car std::array<int, 4>::iterator est un pointeur nu.
        return 0;
    }
    A quoi sert cette différence de règle ?

    Je sais que le code générique pour récupérer un itérateur vers le deuxième élément d'un conteneur de la STL est std::next(conteneur.begin()).
    Mais je trouve dommage qu'il faille passer par un modèle de fonction std::next pour contourner le fait que ++conteneur.begin() ne compile pas avec certains conteneurs. Je pense que cela complique inutilement l'apprentissage de la programmation générique en C++.

  2. #2
    Expert confirmé
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 599
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 599
    Par défaut
    Je poserais plutôt la question inverse :
    Pourquoi peut-on appeler une fonction membre non constante sur une rvalue objet non constante alors que l'on ne peut normalement pas modifier une rvalue ?

    Modifier une rvalue est une source d'erreur, la ligne ++set.begin(); compile et elle pourrait tromper un débutant.

    Une fonction ou un opérateur peut produire une rvalue, mais ne devrait pas permettre d'en modifier une. Les opérateurs des conteneurs devraient être : T& operator++() & et T const& operator++()const & pour n'accepter que les lvalues.

  3. #3
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Citation Envoyé par dalfab Voir le message
    Pourquoi peut-on appeler une fonction membre non constante sur une rvalue objet non constante alors que l'on ne peut normalement pas modifier une rvalue ?
    Cela permet :


    Du coup, je viens de répondre à ma propre question, étant donné que, pour les non-objets :
    • la copie est peu coûteuse et sera probablement optimisée par le compilateur et
    • le named parameter idiom ne s'applique qu'aux objets.


    Mais je trouve toujours dommage que cela complique l'apprentissage de la programmation générique.

  4. #4
    Membre éclairé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2006
    Messages
    232
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2006
    Messages : 232
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    auto setIt = ++set.begin(); // Compile, car std::set<int>::iterator est un objet.
    Attention, ce n'est pas parce que std::set<int>::iterator est un objet que ce code compile, mais parce que l'implémentation de l'opérateur ++ a été réalisé ici en tant que fonction membre.

    S'il avait été défini en tant que fonction libre sous la forme iterator & operator++(iterator &), ce code ne compilerait pas non plus (sauf à définir une surcharge acceptant iterator &&).

  5. #5
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 397
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 397
    Par défaut
    Donc en gros, on ne peut pas passer un temporaire non-nommé à une fonction attendant une référence non-const, mais on peut appeler une fonction membre non-const sur un temporaire non-nommé?
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  6. #6
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    @Médinoc : Ça dépend quelle fonction membre non-const :
    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 A {
        void foo   ()          {}
        void fooLV () &        {}
        void fooRV () &&       {}
        void fooC  () const    {}
        void fooCLV() const &  {}
        void fooCRV() const && {}
    };
     
    void barLV (A&        a) {}
    void barRV (A&&       a) {}
    void barCLV(const A&  a) {}
    void barCRV(const A&& a) {}
     
    int main() {
        A{}.foo   ();
    //  A{}.fooLV (); // Ne compile pas.
        A{}.fooRV ();
        A{}.fooC  ();
        A{}.fooCLV();
        A{}.fooCRV();
    //  barLV (A{}); // Ne compile pas.
        barRV (A{});
        barCLV(A{});
        barCRV(A{});
        return 0;
    }
    Citation Envoyé par dalfab Voir le message
    Les opérateurs des conteneurs devraient être : T& operator++() & et T const& operator++()const & pour n'accepter que les lvalues.
    T const& operator++()const & accepte les rvalues à cause du const.
    Mais une telle fonction n'a pas de raison d'être const, car elle sert habituellement à modifier l'objet.
    Même std::back_insert_iterator::operator++ n'a pas de surcharge const alors qu'il ne fait rien.

  7. #7
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 397
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 397
    Par défaut
    Le & à la fin des déclarations de fonctions membres, c'est du C++11 ou ça a toujours été là? Car c'est ce thread qui m'a appris que ça existait...
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  8. #8
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 760

  9. #9
    Membre éclairé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2006
    Messages
    232
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2006
    Messages : 232
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    Donc en gros, on ne peut pas passer un temporaire non-nommé à une fonction attendant une référence non-const, mais on peut appeler une fonction membre non-const sur un temporaire non-nommé?
    Oui. Cela aboutit à une certaine "asymétrie" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    std::vector<int> get_vect()
    { return std::vector<int>(); }
     
    int main() 
    {
        std::vector<int> v;
        // std::swap(v, get_vect()); // KO
        // v.swap(get_vect());       // KO
        get_vect().swap(v);          // OK !
    }
    Mais il aurait sans doute été une trop grande source d'erreurs de permettre de lier des temporaires à des lvalue références non constantes et trop limitatif de ne pas permettre d’appeler des fonctions membres non const sur ces mêmes temporaires.

    A noter l'astuce employée notamment avec auto_ptr (C++98) avec un opérateur de conversion :
    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
    struct auto_ptr;
     
    struct auto_ptr_ref
    {
        auto_ptr * ref;     
        explicit auto_ptr_ref(auto_ptr * ref): ref(ref)
        {}
    };
     
    struct auto_ptr
    {
        auto_ptr() {}
        auto_ptr(auto_ptr & other) {}    
        auto_ptr(auto_ptr_ref other) {}
     
        operator auto_ptr_ref ()
        { return auto_ptr_ref(this); }
    };
     
    auto_ptr get_auto_ptr()
    { 
        auto_ptr result;
        return result;
    }
     
    int main() 
    {    
        auto_ptr test = get_auto_ptr();  // erreur à la compilation sans le constructeur auto_ptr(auto_ptr_ref other)  
    }
    Le constructeur par copie ne prenant pas une référence constante, il ne peut être utilisé sur un temporaire. En revanche, l'appel implicite à un opérateur de conversion non const est possible !

    Les rvalues références du C++11 permettent maintenant de gérer les temporaires comme on le souhaite.

    Citation Envoyé par Médinoc Voir le message
    Le & à la fin des déclarations de fonctions membres, c'est du C++11 ou ça a toujours été là?
    C'est effectivement un ajout du C++11 (ref-qualifiers) avec la règle suivante : les différentes surcharges d'une fonction membre doivent soit toutes en avoir un (& ou &&), soit aucune.

  10. #10
    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
    Citation Envoyé par gb_68 Voir le message
    Les rvalues références du C++11 permettent maintenant de gérer les temporaires comme on le souhaite.

    C'est effectivement un ajout du C++11 (ref-qualifiers) avec la règle suivante : les différentes surcharges d'une fonction membre doivent soit toutes en avoir un (& ou &&), soit aucune.
    Je viens de relire le sujet, notamment les explications d'Andrzej. Ses explications sont très intéressantes aussi.

    En fait, il est possible d'écrire beaucoup plus de variantes que simplement & et &&:
    1. f();
    2. f() const;
    3. f() volatile;
    4. f() volatile const;
    5. f() &;
    6. f() &&;
    7. f() const&;
    8. f() const&&;
    9. f() volatile &;
    10. f() volatile &&;
    11. f() volatile const&;
    12. f() volatile const&&;

    En effet, les quatre premières sont incompatibles avec les suivantes.

    Et dans l'histoire, je comprends à peu près &, && et const&, mais alors pour const&& et toutes les volatiles (cot?) ca va me demander plus de travail.

Discussions similaires

  1. Réponses: 11
    Dernier message: 15/09/2008, 10h25
  2. Réponses: 25
    Dernier message: 02/08/2008, 17h11
  3. Pourquoi ne peut-on pas instancier de tableau paramétré ?
    Par LGnord dans le forum Collection et Stream
    Réponses: 2
    Dernier message: 28/03/2008, 08h59
  4. ne pas modifier une zone de texte
    Par gailup dans le forum Langage
    Réponses: 3
    Dernier message: 22/06/2006, 19h28

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