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 :

Ecriture générique pour manipuler des (w)string


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 Ecriture générique pour manipuler des (w)string
    J'hésite entre deux écritures pour un même résultat:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template<typename C, typename Tr, typename Al>
    void all_trim(std::basic_string<C, Tr, Al> & s, C const *cs)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template<typename S>
    void all_trim(S & s, S::value_type const *cs)
    Jusqu'à présent j'avais tendance à utiliser la première écriture pour mes petites fonctions de manipulations de (w)string, mais je suis en train de revoir ma copie pour utiliser la deuxième. C'est plus court et plus simple finalement.
    Et ça me permet d'écrire ceci par exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template<typename S>
    S trim(S && s, S::value_type const *cs)
    {
    	S ms(std::forward<S>(s));
    	all_trim(ms, cs);
    	return ms;
    }
    Des avis ?
    Merci.

  2. #2
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 965
    Points
    32 965
    Billets dans le blog
    4
    Par défaut
    Si tu ne comptes utiliser que des std::(w)string, autant aller au plus simple à écrire et comprendre imo.
    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.

  3. #3
    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
    Je croyais tout bon, et puis patatras, ceci ne compile pas:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template<typename S>
    S trim(S && s, S::value_type const *cs)
    {
    	S ms(std::forward<S>(s));
    	all_trim(ms, cs);
    	return ms;
    }
    Visual Studio 2015 me donne ces erreurs:
    error C2672: 'trim': no matching overloaded function found_
    error C2893: Failed to specialize function template 'S trim(S &&,const S::value_type *)'_

    Je dois faire comme ceci
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template<typename S, typename C>
    S trim(S && s, C const *cs)
    {
    	S ms(std::forward<S>(s));
    	all_trim(ms, cs);
    	return ms;
    }
    Mais je ne comprends pas trop pourquoi... (bien que ça ne me dérange pas, mais comprendre c'est toujours bon )

  4. #4
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    Comme tu utilises std::wstring et std::string, tu fournis en réalité une spécialisation de std::basic_string. Si bien que S::value_type n'est en réalité que le typedef que l'on trouve dans std::basic_string qui correspond au type de caractères à utiliser.

    Or, ce typedef est dépendant de la spécialisation de std::basic_string que tu utilise (et qui te sert de paramètre template), tu devrais donc -- si je ne m'abuse -- clairement spécifier qu'il est dépéndant du paramètre template, sous une forme (non testée) qui serait proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template<typename S>
    S trim(S && s, typename S::value_type const *cs)
    {
    	S ms(std::forward<S>(s));
    	all_trim(ms, cs);
    	return ms;
    }
    (d'ailleurs il y a peut-etre bien un typedef plus intéressant à utiliser comme const_pointer)

    Une autre solution pourrait être (histoire de t'assurer que C fait bien partie de la spécialisation de std::basic_string représentée par S) de fournir une valeur par défaut pour C, sous la forme de (toujours non testé)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template<typename S, typename C= typename S::value_type> //  je préférerais vraiment const_pointer à value_type
    S trim(S && s, C const *cs)
    {
    	S ms(std::forward<S>(s));
    	all_trim(ms, cs);
    	return ms;
    }
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #5
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Je préfère ta seconde écriture, en plus d'être plus simple, elle fonctionnera avec plus de types semblables à des strings que std::basic_string. La première n'a d'intérêt à mon sens que si tu veux jouer avec les paramètres, par exemple convertir ta std::basic_string<C, Tr, Al> en std::basic_string<C, AnotherCharTrait, Al>.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  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
    Merci pour vos réponses !
    J'y vois un peu plus clair

    Après d'autres petits tests, la présence ou non du mot clé typename ne change rien, la même erreur de compilation survient.
    C'est la présence de l'opérateur && qui semble perturber le compilateur.

    Je reprends ici le code (adapté à juste titre comme proposé par koala01)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template<typename S>
    inline S trim(S && s, typename S::const_pointer cs)
    {
    	S ms(std::forward<S>(s));
    	all_trim(ms, cs);
    	return ms;
    }
    Ce code n'est accepté que si le paramètre "s" est une rvalue.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template<typename S, typename Cptr = typename S::const_pointer>
    inline S trim(S && s, Cptr cs)
    {
    	S ms(std::forward<S>(s));
    	all_trim(ms, cs);
    	return ms;
    }
    Celui-ci semble toujours être accepté, lvalue ou rvalue pour le paramètre "s".

    Moi qui croyais avoir tout compris, voici une petite zone d'ombre (pas bien grave ).

  7. #7
    Invité
    Invité(e)
    Par défaut
    Bonjour,

    Citation Envoyé par camboui Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template<typename S, typename Cptr = typename S::const_pointer>
    inline S trim(S && s, Cptr cs)
    {
    	S ms(std::forward<S>(s));
    	all_trim(ms, cs);
    	return ms;
    }
    Celui-ci semble toujours être accepté, lvalue ou rvalue pour le paramètre "s".
    Il est toujours accepté car le paramètre template Cptr est déduit à partir du type du paramètre que tu rentres et non du type par défaut que tu lui donnes, retirer = typename S::const_pointer n'y changerait rien.

    Tu as trouvé le pourquoi dans le lien que tu donnes : lorsque tu passes une lvalue, S comporte également la référence. Et pour aller plus loin, rends-toi compte que S ms ainsi que le retour de ta fonction trim() sont de fait également des références de ton entrée, qui se voit ainsi modifiée. Je ne suis pas certain que ce soit réellement ce que tu souhaites.

    Dans tous les cas std::remove_reference à la rescousse

  8. #8
    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
    Oufti
    Mon dieu, la grosse bourde
    Heureusement que tu passais par ici, toi

    Voici donc le "bon" code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    template<typename S>
    inline std::remove_reference_t<S>
    	trim(S && s, typename std::remove_reference_t<S>::const_pointer cs)
    {
    	std::remove_reference_t<S> ms(std::forward<S>(s));
    	all_trim(ms, cs);
    	return ms;
    }
    Testé et... je vous laisse le soin d'approuver.

  9. #9
    Invité
    Invité(e)
    Par défaut
    Aurais-je oublié de mentionner que les cv-qualifiers étaient également conservés ?
    Ex. : que se passe-t-il si tu passes std::string const str en paramètre ?

    Petit tableau récapitulatif :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int x = 22;
    int & rx = x;
    int const cx = x;
    int const & rcx = x;
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template <typename T>
    void f(T && param);
    f(x); Tint &.
    param est de type int &
    f(rx); Tint &.
    param est de type int &
    f(cx); Tint const &.
    param est de type int const &
    f(rcx); Tint const &.
    param est de type int const &
    f(22); Tint.
    param est de type int &&
    Dernière modification par Invité ; 16/10/2016 à 19h29.

  10. #10
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 965
    Points
    32 965
    Billets dans le blog
    4
    Par défaut
    Pourquoi utiliser && ici ?
    je ne comprends pas l'utilité, si on doit utiliser remove reference et travailler sur une copie, pourquoi ne pas simplement passer un const& ou une copie directement ?
    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.

  11. #11
    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
    On travaille sur une copie si le paramètre est une lvalue.
    Si le paramètre est une rvalue, on travaille sur celle-ci directement. C'est d'ailleurs pour ça que j'ai mis le std::forward (sans lui le passage du paramètre via && ne sert effectivement plus à rien).

  12. #12
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    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 : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    En créant ms, tu travaille toujours sur une copie.

  13. #13
    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
    Non.

    Voici un code pour le prouver (la fonction template test_moved_param(S && s, char const *val) est semblable à la fonction template trim(S && s, typename std::remove_reference_t<S>::const_pointer cs) précédante).
    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
    class full_object
    {
    	std::string last_call;
    	std::string m_value;
    public:
    	full_object() : last_call("Default constructor"),
    		m_value()
    		{ display(); }
    	full_object(const full_object & fo) : last_call("Copy constructor"),
    		m_value(fo.m_value)
    		{ display(); }
    	full_object(full_object && fo) : last_call("Move constructor"),
    		m_value(std::move(fo.m_value))
    		{ display(); }
    	full_object & operator=(const full_object & fo)
    	{
    		last_call = "Copy assignment operator";
    		m_value = fo.m_value;
    		display();
    		return *this;
    	}
    	full_object & operator=(full_object && fo)
    	{
    		last_call = "Move assignment operator";
    		m_value = std::move(fo.m_value);
    		display();
    		return *this;
    	}
    	virtual ~full_object()
    	{
    		last_call = "Destructor";
    		display();
    	}
     
    	full_object & assign(char const *val)
    		{ m_value = val; return *this; }
    	full_object & append(char const *val)
    		{ m_value += val; return *this; }
    	full_object & assign(const full_object & fo)
    		{ m_value = fo.m_value; return *this; }
    	full_object & append(const full_object & fo)
    		{ m_value += fo.m_value; return *this; }
    	void display()
    		{ std::cout << get() << std::endl; }
    	std::string get()
    		{ return last_call + " - value=" + m_value; }
    };
     
    full_object operator+(const full_object & lfo, const full_object & rfo)
    {
    	full_object tfo;
    	tfo.assign(lfo);
    	tfo.append(rfo);
    	return tfo;
    }
     
    template<typename S>
    inline std::remove_reference_t<S>
    	test_moved_param(S && s, char const *val)
    {
    	std::remove_reference_t<S> ms(std::forward<S>(s));
    	ms.assign(val);
    	return ms;
    }
     
    int main()
    {
    	full_object fo1;
    	std::cout << ".\t" << fo1.get() << std::endl;
    	fo1.assign("fo1");
    	std::cout << ".\t" << fo1.get() << std::endl;
    	full_object fo2 = test_moved_param(fo1, "fo1, lvalue");
    	std::cout << ".\t" << fo2.get() << std::endl;
    	full_object fo3 = test_moved_param(fo1.append("/").append(fo2), "fo1.append(\"/\").append(f02), lvalue");
    	std::cout << ".\t" << fo3.get() << std::endl;
    	full_object fo4 = test_moved_param(fo2.append("|") + fo3, "fo2+fo3, rvalue");
    	std::cout << ".\t" << fo4.get() << std::endl;
    }
    Et voici l'output:
    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
    Default constructor - value=
    .       Default constructor - value=
    .       Default constructor - value=fo1
    Copy constructor - value=fo1
    Move constructor - value=fo1, lvalue
    Destructor - value=
    .       Move constructor - value=fo1, lvalue
    Copy constructor - value=fo1/fo1, lvalue
    Move constructor - value=fo1.append("/").append(f02), lvalue
    Destructor - value=
    .       Move constructor - value=fo1.append("/").append(f02), lvalue
    Default constructor - value=
    Move constructor - value=fo1, lvalue|fo1.append("/").append(f02), lvalue
    Destructor - value=
    Move constructor - value=fo1, lvalue|fo1.append("/").append(f02), lvalue
    Move constructor - value=fo2+fo3, rvalue
    Destructor - value=
    Destructor - value=
    .       Move constructor - value=fo2+fo3, rvalue
    Destructor - value=fo2+fo3, rvalue
    Destructor - value=fo1.append("/").append(f02), lvalue
    Destructor - value=fo1, lvalue|
    Destructor - value=fo1/fo1, lvalue
    En gras et en couleur l'affichage lorsque le code std::remove_reference_t<S> ms(std::forward<S>(s)); est executé.
    En vert on s'attend à une lvalue et c'est bien le Copy Construtor qui est appelé.
    En rouge on s'attend à une rvalue et c'est bien le Move Construtor qui est appelé.
    On voit donc bien que les rvalue ne sont pas copiées et restent des rvalue.

  14. #14
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    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 : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    Ici, il faut prendre le "une copie", comme "une autre instance". Ton message précédent sous-entend que si le paramètre est une rvalue, le paramètre est utilisé directement, or non, il y a création d'une nouvelle instance, d'où le "une copie" qui est ici ambigu.

    Ce que dit @Bousk et que si tu utilises toujours une nouvelle instance, autant prendre un type plein en paramètre (= un type sans référence). Ainsi, il n'y a plus besoin de créer une nouvelle instance et tu simplifies le code.
    Et tu profites également de la copie élision, ce qui veut dire que pour auto s = trim("abc"s); et avec la RVO (une sorte de copie élision pour le retour de fonction), il y un constructeur avec une chaîne, 0 constructeur de copie, 0 constructeur de déplacement, contrairement à la version avec std::forward qui utilise un constructeur de déplacement.

  15. #15
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 965
    Points
    32 965
    Billets dans le blog
    4
    Par défaut
    Merci jo, je pensais devenir fou.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    trim(S && s, typename std::remove_reference_t<S>::const_pointer cs)
    {
    	std::remove_reference_t<S> ms(std::forward<S>(s));
    est exactement similaire à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    trim(S ms, typename S::const_pointer cs)
    {
    si ma santé mentale n'est pas encore défaillante.

    edit: bien vu, je l'avais loupé
    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.

  16. #16
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    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 : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    On peut même éjecter std::remove_reference.

  17. #17
    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
    Vous avez raison.
    A force de vouloir caser ce foutu && j'en oublie de faire simple pour faire compliqué.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template<typename S>
    inline S test_moved_param(S ms, char const *val)
    {
    	ms.assign(val);
    	return ms;
    }
    Résultat:
    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
    Default constructor - value=
    .       Default constructor - value=
    .       Default constructor - value=fo1
    Copy constructor - value=fo1
    Move constructor - value=fo1, lvalue
    Destructor - value=
    .       Move constructor - value=fo1, lvalue
    Copy constructor - value=fo1/fo1, lvalue
    Move constructor - value=fo1.append("/").append(f02), lvalue
    Destructor - value=
    .       Move constructor - value=fo1.append("/").append(f02), lvalue
    Default constructor - value=
    Move constructor - value=fo1, lvalue|fo1.append("/").append(f02), lvalue
    Destructor - value=
    Move constructor - value=fo2+fo3, rvalue
    Destructor - value=
    .       Move constructor - value=fo2+fo3, rvalue
    Destructor - value=fo2+fo3, rvalue
    Destructor - value=fo1.append("/").append(f02), lvalue
    Destructor - value=fo1, lvalue|
    Destructor - value=fo1/fo1, lvalue
    Je vais un faire un peu de "C" avec de z'olis pointeurs pour me changer les idées

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

Discussions similaires

  1. Bibliotheque pour manipuler des buffer string
    Par Tail dans le forum Bibliothèque standard
    Réponses: 1
    Dernier message: 09/08/2009, 20h57
  2. Meilleure methode pour manipuler des images
    Par etranger dans le forum Modules
    Réponses: 2
    Dernier message: 16/02/2007, 13h06
  3. API pour manipuler des RPM
    Par pcery dans le forum API standards et tierces
    Réponses: 1
    Dernier message: 07/08/2006, 14h44
  4. Quel langage pour manipuler des entiers très longs ?
    Par mis_dj dans le forum Langages de programmation
    Réponses: 8
    Dernier message: 10/05/2006, 21h12
  5. Delphi 2005 pour manipuler des formulaires MS-Access ??
    Par Mustard007 dans le forum Bases de données
    Réponses: 4
    Dernier message: 18/10/2005, 21h39

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