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 :

[C++11] std::forward et "transitivité"


Sujet :

Langage C++

  1. #1
    Membre émérite Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Par défaut [C++11] std::forward et "transitivité"
    Hello.
    Je sais, le titre n'est pas très compréhensible.
    Mais c'est la seule chose qui m'est venue à l'esprit pour résumé ma question.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void f(unType&& x)
    {
        g(x);
    }
     
     
    void g(unType&& x)
    {
        // Utilisation de x...
    }
    Ma question est la suivante : sachant que la fonction g n'est pas surchargée, est-il nécessaire/utile/indispensable/superflu... d'appeler std::forward à l'intérieur de f ?
    [[ g(std::forward<unType>(x)); ] au lieu de [ g(x)); ]]

    Merci de vos conseils avisés, ou de vos éclaircissements...

  2. #2
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut
    Sans std::forward ou std::move ça ne compilera pas.

    A l'intérieur de f la variable x est une *lvalue*, car elle a un nom. (rvalue = temporaire *anonyme*).
    Donc si g ne possède que la surcharge avec rvalue reference, la ligne g(x); ne compilera pas vu que tu essayes de passer une lvalue à une fonction qui n'accepte que des rvalue.

    J'utiliserais plutôt std::move(x) dans ce cas, qui est en fait synonyme de "compilateur, je sais ce que je fais, considère que x est une rvalue" alors que std::forward est plutôt utilisé dans le cas de l'idiome du perfect forwarding.

  3. #3
    Membre émérite Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Par défaut
    Ok, merci.

    Le problème avec les lvalue, les rvalue, les « références à double & » (je ne sais pas comment ça s'appelle), std::move et std::forward, c'est que je vois à peu ce que c'est, mais que je ne vois pas vraiment à quoi ça correspond.
    (En gros, je n'ai qu'une vague idée de ce que c'est, et je ne vois pas vraiment comment ça fonctionne.)

    J'ai vu que dans la STL (du moins pour g++), ils utilisent std::forward dans le « move constructor », alors je pensais que ça voulait dire :
    « Attention, le contenu de cette *variable* va être déplacé un peu plus tard, je ne fais que passer le relai. »
    Et aussi que std::move c'était pour effectivement déplacer le contenu.

    J'ai un peu de mal à trouver des explications claires sur la nouvelle norme.
    Enfin c'est surtout que je ne sais pas où chercher.
    Quant à ce que j'ai lu de la norme elle-même ou des propositions, en général ça m'embrouille plus que ça ne m'éclaire.

    Alors si une bonne âme voulait bien m'aider à comprendre ce qu'il y a à savoir sur les cinq points pré-cités...

  4. #4
    Membre Expert

    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 : 34
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Bonjour,

    rvalue = qui doit être à droite d'un signe =
    lvlaue = qui peut être à gauche d'un signe =

    Les rvalue references (rref = &&, les autres sont des lvalue references, lref = &), permettent premièrement de résoudre un problème du C++03 : on ne peut pas initialiser un référence non constante sur un temporaire. Un temporaire est une rvalue, et une lref (hors constante) doit être initialisé avec une lvalue. Avec les rref ca devient possible.

    Ensuite ils permettent d'introdire la move-semantic, ca a divers avantages dont une semantique comme l'illustre std::unique_ptr qui vient remplacer std::auto_ptr. L'utilisation de la move-semantic (move-ctor et move-assign) rend l'utilisation de std::unique_ptr bien plus clair que celle de auto_ptr.

    std::move, ca permet juste de transformer une lvalue en rvalue, et c'est justement le fait qu'on doivent utiliser ceci pour affecter un unique_ptr à un autre qui rend sont utilisation plus clair : le fait d'écrire std::move indique clairement que la variable qu'on utilise pour l'affectation va être "déplacé".

    std::forward permet de "faire suivre" une variable, de rester une lvalue si c'est une lvalue et une ravalue si c'est une rvalue :
    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
     
    #include<utility>
    #include<iostream>
     
    void bar(int&&) //si on passe un rvalue à bar
    { std::cout << 0; }
     
    void bar(int&) //si on passe une lvalue à bar
    { std::cout << 1; }
     
    template<class T>
    void foo(T&& t)
    { bar(std::forward<T>(t)); }
     
    int main()
    {
      int = 0;
      foo(0); //T = int, type de t = int&&, rvalue
      foo(i); //T = int&, type de t = int& (int& && = int&), lvalue
    }

  5. #5
    Membre émérite Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Par défaut
    Ok, je vois.
    C'est beaucoup plus clair à présent.
    Merci beaucoup !

  6. #6
    Membre Expert

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    rvalue = qui doit être à droite d'un signe =
    THE IRONY !

    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  7. #7
    Membre émérite Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Par défaut
    Là, je suis perplexe...
    Le code de Flob90 fonctionne très bien chez moi, mais pas celui-ci :
    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
    #include <iostream>
    #include <utility>
     
     
     
    inline
    std::ostream& endf(std::ostream& os)
    {
        return std::endl(os << ')');
    }
     
     
    #define AFF std::cout << __func__ << '('
     
     
    inline
    void h(int const& n)
    {
        AFF << "int const& = " << &n << endf;
    }
     
     
    inline
    void h(int&& n)
    {
        AFF << "int&& = " << &n << endf;
    }
     
     
    inline
    void f(int&& n)
    {
        AFF << "int&& = " << &n << endf;
        return h(std::forward<int>(n));
    }
     
     
     
    inline
    void g(int&& n)
    {
        AFF << "int&& = " << &n << endf;
        return h(std::move(n));
    }
     
     
     
    int main()
    {
        int n = 7;
        f(3);
        f(n);
        g(-9);
        g(n);
        return 0;
    }
    toto.cpp: In function ‘int main()’:
    toto.cpp:48:8: error: cannot bind ‘int’ lvalue to ‘int&&’
    toto.cpp:28:6: error: initializing argument 1 of ‘void f(int&&)’
    toto.cpp:50:8: error: cannot bind ‘int’ lvalue to ‘int&&’
    toto.cpp:36:6: error: initializing argument 1 of ‘void g(int&&)’
    Qu'est-ce que j'ai fait de différent ???

  8. #8
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut
    Mince, nous sommes eu ! Je soupçonne flob90 d'avoir un peu éludé sa réponse, tout comme la mienne, car les règles complètes sont longes et chiantes à expliquer.

    En réalité :

    1 : f(int& truc);
    f n'accepte QUE des lvalue.
    Rien de nouveau, c'est une règle classique du C++.
    Si l'on tente de passer une rvalue, la compil échoue : f(5); // fail

    2 : g(int&& truc);
    g n'accepte QUE des rvalue.
    Et par exemple interdit : int n = 7; g(n); //fail car n est une lvalue.

    C'est le but premier d'une surcharge avec rvalue reference en fait, pouvoir discriminer lors de l'appel un paramètre passé en tant que lvalue ou en tant que rvalue.

    EXCEPTION !!
    On ne serait pas en C++ s'il n'y avait pas quelques exceptions tordus.
    Il se trouve qu'avec les rvalue reference, le comité C++ a voulu résoudre deux problème en un : la move semantic et le perfect forwarding. (j'ai écrit ça il y a quelques temps sur le perfect forwading si tu veux plus d'info)
    Pour résoudre le perfect forwarding, ils n'ont pas trouvé d'autre solution que d'ajouter l'exception suivante :
    Si une fonction f prend pour paramètre une rvalue reference ET si la fonction est une fonction template alors les règles sont différentes !! Dans ce cas et dans ce cas uniquement le compilateur active des règles spéciales. Grâce à ces règles spéciales, le code montré par Flob90 avec std::forward fonctionne et transmet correctement la lvalueness et la rvalueness.

    Malheureusement ces règles spéciales ont aussi pour effet secondaire d’autoriser :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    template <typename T>
    void f(T&& t)
    {
    }
    int n = 4;
    f(n); // compile alors que n est une lvalue !
    Alors qu'on a vu qu'une fonction non-template n'acceptait que des rvalue dans ce cas.

    J'aurais très largement préféré que le comité trouve une solution pour le perfect forwarding qui soit orthogonale aux rvalue reference et donc qu'une fonction template prenant des rvalue reference n'accepte QUE des rvalue comme pour les fonction normales, mais, malheureusementn changer un langage aussi complexe que le C++ est presque aussi impossible que de résoudre la quadrature du cercle, et le seul moyen que le comité ait trouvé pour intégrer le perfect forwarding a été de faire un peu de chirurgie dans le cas rvalue reference+template...

  9. #9
    Membre Expert

    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 : 34
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Citation Envoyé par Arzar Voir le message
    Dans ce cas et dans ce cas uniquement le compilateur active des règles spéciales.
    Pour expliciter un peu la règle à l'OP, elle dit que lors de la déduction des arguments templates (cette partie du C++ est plein de règles assez peu intuitives je trouve), si l'on passe une lvalue à une fonction template qui a un rref template argument le paramètre template est déduit comme une lref.

    Pour tes erreurs ce sont les règles "normales" comme l'a détaillé Arzar.

  10. #10
    Membre Expert

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Par défaut
    Citation Envoyé par Emmanuel Deloget Voir le message
    Citation Envoyé par Flob90 Voir le message
    rvalue = qui doit être à droite d'un signe =
    THE IRONY !

    Pour ceux qui aurait pu penser que je manquait de respect à Flob90, ce n'est pas le cas (très, très, très loin de là ; il fait partie de la quelquezaine de posters de DVP.com que je respecte le plus). Histoire de lever tout doute sur ma réponse, voici une courte explication.

    Dans la phrase de Flob90 ci-dessus, qui est entièrement exacte, le terme rvalue est à gauche du signe égal, en contradiction avec ce que dit la phrase (qui je le répète, est exacte). La forme de la phrase, en contradiction avec son fond, montre donc une certaine ironie que dans ma grande bassesse (et après quelques heures ininterrompue de travail) je n'ai pas pu m'empêcher de relever.
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  11. #11
    Membre émérite Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Par défaut
    Je comprend mieux.
    J'avais bien remarqué que la seule différence était la présence/l'absence de template, mais je ne pensais pas que ça jouait...

    Donc pour résumer, si je comprends bien, je dois écrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void exterieur(unType const& x)
    {
        (...)
        interieur(x);
        (...)
    }
     
    void exterieur(unType&& x)
    {
        (...)
        interieur(std::move(x));
        (...)
    }
    Par contre, ce code est superflu :
    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
    template <typename T>
    void exterieur(T const& x)
    {
        (...)
        interieur(x);
        (...)
    }
     
    template <typename T>
    void exterieur(T&& x)
    {
        (...)
        interieur(std::move(x));
        (...)
    }
    et peut être remplacé par :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template <typename T>
    void exterieur(T&& x)
    {
        (...)
        interieur(std::forward<T>(x));
        (...)
    }
    J'ai bon ? J'ai bon ?

  12. #12
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut
    Presque
    Par contre, ce code est superflu :
    Il n'est pas superflu, il est faux
    Enfin bon, faux n'est pas vraiment le mot correct, car le code compile mais il est très traitre avec des résultats surprenant, car différent du cas non-template, à cause du cas spécial template + rvalue reference.
    C'est à dire que le code suivant :
    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
    void interieur(const int& i)
    {
       std::cout << "interieur const int& \n";
    }
     
    void interieur(int&& i)
    {
       std::cout << "interieur int&& \n";
    }
     
    template <typename T>
    void exterieur(T const& x)
    {
        interieur(x);
    }
     
    template <typename T>
    void exterieur(T&& x)
    {
        interieur(std::move(x));
    }
     
    int main()
    {
       int n = 7;
       exterieur(n);
       exterieur(5);
    }
    affiche :
    interieur int&&
    interieur int&&
    Mais sinon,ton résumé est correct on doit effectivement :

    * Dans la cas non template, traiter le cas lvalue/rvalue avec deux surcharges différentes. En pratique c'est quasiment toujours ce que l'on veut faire, car on n'écrit des surcharges lvalue ref/rvalue ref quasiment que lors d'écriture de move constructor/move assignement op et dans ce cas on souhaite justement bien faire la distinction pour pouvoir faire des optimisations dans le cas rvalue (move semantic))

    * Dans le cas template, forwarder avec std::forward, ce qui va transmettre à la fonction "interieur" sans perdre la lvalueness/rvalueness de l'expression passée à la fonction "exterieur". Et pratique c'est aussi majoritairement ce que l'on souhaite faire avec des fonctions template. (voir par exemple dans l'article que j'ai linké plus haut l'exemple des nouvelles fonctions emplace dans la STL ainsi que std::make_shared)

    Donc bon, les règles n'ont pas été choisi au pif non plus, le comité a adapté les règles en fonction de l'usage qu'il pressentait majoritaire.

  13. #13
    Membre émérite Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Par défaut
    Très bien.
    Marchi à vous deux !!!

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

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