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

C++ Discussion :

lvalue, rvalue et template


Sujet :

C++

  1. #1
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut lvalue, rvalue et template
    Le move semantics et l'opérateur && sont, je trouve, un concept assez complexe à mettre en oeuvre alors que le besoin de base est assez élémentaire.
    A y bien regarder, l'opérateur && ne me parait utile que dans un contexte générique (template).
    Voici un bout de code:
    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
    void fooint(int && i)
    {
    	std::cout << i << std::endl;
    }
    
    void foostr(std::string && s)
    {
    	std::cout << s << std::endl;
    }
    
    template<typename T>
    void fooT(T && t)
    {
    	std::cout << t << std::endl;
    }
    
    template<>
    void fooT(int && t)
    {
    	std::cout << t << std::endl;
    }
    
    int main()
    {
    	int i = 25;
    	fooint(i);//(1) 'void fooint(int &&)': cannot convert argument 1 from 'int' to 'int &&'
    	fooint(i + 10);
    	fooT(i);//(3) OK !
    	fooT<int>(i);//(2) 'void fooT<int>(int &&)': cannot convert argument 1 from 'int' to 'int &&'
    
    	std::string s("abc", 3);
    	foostr(s);//(1) 'void foostr(std::string &&)': cannot convert argument 1 from 'std::string' to 'std::string &&'
    	foostr(s + "10");
    	fooT(s);//(3) OK !
    
    	return 0;
    }
    Aux endroids (1) et (2) ça ne compile pas.
    En (1) car passer une lvalue par rvalue n'est pas autorisée.
    En (2) non plus, même en cas de spécialisation de template.
    Il n'y a que en (3) qu'on peut passer une lvalue en rvalue, en template "pure" si j'ose dire.

    Et d'une manière générale, il n'y a que dans les templates qu'on peut passer une lvalue ou une rvalue en rvalue.

    Ai-je bien tout compris ?
    Merci

  2. #2
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Tu n'es pas loin d'avoir bien compris. En effet, ce n'est pas une très bonne pratique d'utiliser des rvalue explicite, sauf dans le cas où tu déclares/définis des constructeurs par déplacement ou des opérateurs d'affectation par déplacement, ou si tu utilises des refs qualifiers (voir plus loin dans ma réponse). Dans un appel de fonction (membre ou pas), ça n'apporte rien en terme de design et c'est plutôt suspect.

    Citation Envoyé par camboui Voir le message
    Et d'une manière générale, il n'y a que dans les templates qu'on peut passer une lvalue ou une rvalue en rvalue.
    C'est un poil plus subtil que cela. En fait, dans un template, la notation T&& ne veut pas dire que c'est une rvalue reference, cela veut dire que selon le contexte, il peut être résolu comme un T (passage par valeur, adapté à une rvalue) ou un T& (passage par référence, adapté à une lvalue). Cela n'a de sens, bien sûr, que si la spécialisation n'est pas explicite.

    La doc à ce sujet, un paragraphe détaille ce point.

    Un autre usage, non template et très utile mais moins connu, sont les refs qualifiers (C++14). Au lieu d'écrire :

    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    struct A {};
     
    struct B {
      A value;
     
      A& get();
      A const& get() const;
    };

    Qui n'est pas "rvalue friendly", on peut écrire :

    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct A {};
     
    struct B {
      A value;
     
      A& get() &;
      A const& get() const &;
      A&& get() &&;
    };

    Du coup, lorsque get() est appelé sur une rvalue référence de B , c'est bien la troisième surcharge qui est utilisée, et non pas la première comme dans le cas classique.
    Find me on github

  3. #3
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    Citation Envoyé par jblecanard Voir le message
    il peut être résolu comme un T (passage par valeur, adapté à une rvalue)
    C'est evalue comme une rvalue ref.

    Citation Envoyé par camboui Voir le message
    A y bien regarder, l'opérateur && ne me parait utile que dans un contexte générique (template).
    Elle est utile a chaque fois que l'on doit faire une surcharge de fonction qui sera spécifique pour les lvalue ou les rvalues. Cf par exemple std::vector, les rvalue refs sont utilisés dans les constructeurs, l'operateur =, les fonctions insert et push_back.

    Je ne vois pas trop d'exemple d'utilisation de rvalue refs sans surcharge (hors template), a mon avis, cela n'a pas trop de sens. (Ou alors cas tres exceptionnels ? Quelqu'un va bien trouver un exemple ?).

    Les templates (universal refs) ont effectivement l'avantage de s'utiliser avec des lvalue et rvalue, mais encore faut-il que cela ait un sens. Dans tes codes par exemple, tu peux utiliser des const refs, tu auras le meme comportement.
    Les universal refs seront utiles par exemple avec du perfect forwarding.

    Citation Envoyé par camboui Voir le message
    Le move semantics et l'opérateur && sont, je trouve, un concept assez complexe à mettre en oeuvre alors que le besoin de base est assez élémentaire.
    La move est utile pour la gestion fine de la memoire et des objets. Donc oui, c'est forcément plus complexe a mettre en oeuvre, puisque la gestion fine de la mémoire et des objets est complexe.
    Mais pour beaucoup d'utilisateurs, les besoins de base seront réglés sans utiliser de move explicite, simplement en utilisant les capsules RAII existantes.

  4. #4
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 629
    Points : 10 554
    Points
    10 554
    Par défaut
    Citation Envoyé par mintho carmo Voir le message
    Je ne vois pas trop d'exemple d'utilisation de rvalue refs sans surcharge
    Je jette une bouteille ... et je vais me prendre une volée de -1

    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
    class A { /**/ };
     
    A add_2_A(A& a1, A& a2) {
        A tmp =  a1.add(a2);
     
        return tmp;
    }
     
    //...
     
        A a, b;
     
    //...
     
        A c = add_2_A(a, b);

  5. #5
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    Il n'y a pas de rvalue ref dans ton code. Et pour bien faire les choses, on pourrait utiliser une surcharge ici.

    J'avais pensé aussi aux fonctions de la STL qui prennent des fonctions en paramètre (std::no_fn, std::invoke) ou a std::exchange, mais c'est des universal refs.

  6. #6
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Citation Envoyé par mintho carmo Voir le message
    Elle est utile a chaque fois que l'on doit faire une surcharge de fonction qui sera spécifique pour les lvalue ou les rvalues. Cf par exemple std::vector, les rvalue refs sont utilisés dans les constructeurs, l'operateur =, les fonctions insert et push_back.
    J'aime assez bien aussi le nouveau membre emplace_back()

  7. #7
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Merci à tous. Je crois que j'en sais suffisamment pour aller de l'avant (je verrai les refs qualifiers plus tard qui semblent aussi avoir leur intérêt).
    Je me suis fait un petit exercice (avec plusieurs concepts par ailleurs, SFINAE, variadic, etc).
    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
    template<class ...T>
    struct MonRecursif;
     
    template<>
    class MonRecursif<>
    {
    public:
    	MonRecursif() = default;
    	template <class S>
    	void foo(S & s) const
    	{
    		s << std::endl;
    	}
    };
     
    template<class T, class ...R>
    class MonRecursif<T, R...> : private MonRecursif<R...>
    {
    	typedef MonRecursif<R...> parent_type;
    	static const size_t s_size = 1 + sizeof...(R);
     
    	T f_val;
    public:
    	template<class T2, class ...R2>
    	MonRecursif(T2 && v, R2 &&... r) :
    		parent_type(std::forward<R2>(r)...), f_val(std::forward<T2>(v))
    	{}
    	template <class S>
    	typename std::enable_if_t<(s_size > 1)>
    		foo(S & s) const
    	{
    		s << f_val << _T(' ');
    		MonParent().foo(s);
    	}
    	template <class S>
    	typename std::enable_if_t<s_size == 1>
    		foo(S & s) const
    	{
    		s << f_val;
    		MonParent().foo(s);
    	}
    	parent_type const & MonParent() const
    	{
    		return (*this);
    	}
    };
     
    template<class ...T>
    inline MonRecursif<T...> make_MonRecursif(T &&... v)
    {
    	return MonRecursif<T...>(std::forward<T>(v)...);
    }
    Le constructeur de la class MonRecursif est désormais générique et accepte les rvalue et lvalue en fonction du contexte. Ce genre de constructeur est assez récurrent pour initialiser les membres d'une class, une telle écriture peut se généraliser je pense.

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

Discussions similaires

  1. Réponses: 2
    Dernier message: 07/08/2014, 14h35
  2. lvalue rvalue ?
    Par FoX_*D i E* dans le forum Débuter
    Réponses: 4
    Dernier message: 23/12/2012, 11h41
  3. rvalue reference et lvalue
    Par guillaume07 dans le forum C++
    Réponses: 4
    Dernier message: 08/12/2010, 12h17
  4. Rvalues et Lvalues
    Par deubelte dans le forum Débuter
    Réponses: 1
    Dernier message: 29/04/2010, 16h36

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