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 :

Comment rendre un paramêtre template déductible


Sujet :

Langage C++

  1. #1
    Membre régulier
    Comment rendre un paramêtre template déductible
    Bonjour à tous!

    l'exemple suivant produit une erreur car T n'est pas déductible dans la spécialisation partielle.
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    template <typename T>
    struct A {
        int F(T &c) { /* actions */ }
    };
     
    template <class T>
    struct A<std::is_member_function_pointer<typename T::G>> {
        int F(T &c) { /* actions */return c.G(); }
    };

    Mon besoin :
    Je souhaiterais spécialiser A de sorte que lorsque le type de l'argument a une fonction G, A puisse l'utiliser.
    Comment faire pour contourner cette erreur ?

    Par avance merci!

  2. #2
    Membre régulier
    Bonjour

    La spécialisation d'une classe générique est réalisée à la compilation. Tu n'as pas besoin de tester que la classe T possède une fonction G(). Si ce n'est pas le cas, ça ne compilera pas.
    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
    template <class T>
    struct A {
        int F1(T &c) { 
            /* actions */ 
            return c.G();
        };
        int F2(T &c){return 67890;}
    };
     
    class B{
    public:
        int G(){return 12345;};
    };
     
    class C{};
     
    B b;
    A<B> a_B;
    std::cout << a_B.F1(b) << std::endl; // affiche 12345
     
    C c;
    A<C> a_C;                            // il est possible de spécialiser A avec C
    std::cout << a_C.F2(c) << std::endl; // affiche 67890
    std::cout << a_C.F1(c) << std::endl; // Erreur de compilation : In instantiation of 'int A<T>::F1(T&) [with T = C]': required from here error: 'class C' has no member named 'G'

  3. #3
    Membre expert
    Si tu veux faire une dépendance sur 2, il faut ajouter un autre type avec une valeur par défaut et vérifier les dépendances dans ce dernier.

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    template <class T, class = void>
    struct A;
     
    template <class T>
    struct A<T, std::enable_if_t<std::is_member_function_pointer_v<decltype(&T::G)>>> {
        int F(T &c) { /* actions */return c.G(); }
    };


    enable_if_t va retourner void si la condition est respectée ce qui revient à faire une spécialisation sur A<T, void> avec condition.

  4. #4
    Expert éminent sénior
    Salut,

    Ce qui est cool, en C++, c'est que tu peux tout à fait décider de créer une fonction template dans une structure qui ne l'est pas, et que tu peux même te permettre d'en donner plusieurs versions différentes (au travers des paramètres) qui ne seront "activées" que si le type du paramètre fournis répond à certaines obligations.

    Par exemple, tu pourrais très bien créer une politique proche de
    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
    struct FooBarSelector{
        template <typename T>
        static void check(T & u, 
                          typename std::enable_if<
                        std::is_member_function_pointer< decltype(&T::foo)>::value>::type * = nullptr){
            std::cout<<"having just foo\n";
            u.foo();
        }
        template <typename T>
        static void check(T & u, 
                          typename std::enable_if<
                              std::is_member_function_pointer< decltype(&T::bar)>::value>::type * = nullptr){
            std::cout<<"having just bar\n";
            u.bar();
        }
    };

    qui appellera soit la fonction membre foo soit la fonction membre bar, en fonction de celle qui est exposée par différents types de données, comme par exemple :
    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
     
    struct A{
        void foo(){
            std::cout<<"in foo\n";
        };
    };
    struct B{
        void bar(){
            std::cout<<"in bar \n";
        };
    };
     
    int main(){
        A a;
        B b;
        FooBarSelector::check(a);
        FooBarSelector::check(b);
    }
    qui te donnerait une sortie proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    $ ./a.out 
    having just foo
    in foo
    having just bar
    in bar

    Tu pourrais aussi tout à fait envisager de créer une structure template qui implique de fournir le type adéquat, en corrigeant un tout petit peu le code pour lui donner la forme de
    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
    template <typename T>
    struct FooBarSelector{
        template <typename U= T> // pour forcer le paramètre à être du type attendu
        static void check(U & u, 
                          typename std::enable_if<
                        std::is_member_function_pointer< decltype(&U::foo)>::value>::type * = nullptr){
            std::cout<<"having just foo\n";
            u.foo();
        }
        template <typename U = T>  // pour forcer le paramètre à être du type attendu
        static void check(U & u, 
                          typename std::enable_if<
                              std::is_member_function_pointer< decltype(&U::bar)>::value>::type * = nullptr){
            std::cout<<"having just bar\n";
            u.bar();
        } 
    };
    mais cela t'obligerait à spécifier le type de substitution au niveau de la structure (ce qui peut être intéressant dans certaines circonstances) sous une forme d'utilisation qui deviendrait alors proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    int main(){
        A a;
        B b;
        FooBarSelector<A>::check(a);
        FooBarSelector<B>::check(b);
    }
    NOTA: On pourrait alors créer une fonction libre template qui pourrait s'occuper de cacher cette spécialisation de FooBarSelector, sous une forme qui serait proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template <typename T>
    inline void selectFooOrBar(T & t){
        FooBarSelector<T>::check(t):
    }
    et qui pourrait alors petre utilisée sous la forme de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    int main(){
        A a;
        B b;
        selectFooOrBar(a);
        selectFooOrBar(b);
    }

    Enfin, il faut savoir que cela permet de choisir d'appeler foo OU bar selon la disponibilité des fonctions.

    A cause des restrictions dues à la présence de std::is_member_function_pointer (dont la compilation échoue lorsque la fonction n'est pas présente), je n'ai trouvé aucun moyen de permettre de choisir entre foo, bar ou les deux (si une structure venait à exposer les deux fonctions en même temps). du moins, pas en utilisant std::is_member_function_pointer.

    Une alternative possible serait de passer par des "type lists" qui contiendrait la liste de ... tous les types répondant à des besoins particuliers, et de mettre en place une politique d'appel des différentes fonctions à partir de ces listes.

    Le code étant un peu plus complexe à mettre en place, il me faudra un peu de temps pour t'en présenter un exemple fonctionnel
    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
    Expert éminent sénior
    Bon, ben, finalement, j'y suis arrivé beaucoup plus vite que je ne l'aurais cru...

    Mais cela mérite quelques explications

    L'idée est de créer des listes de types qui correspondent à certains critères "arbitraires" (comme le fait d'exposer une fonction bien particulière, par exemple), puis de faire en sorte que le compilateur choisisse automatiquement la bonne réaction sur base de la présence (ou non), dans cette liste du type du paramètre transmis à une fonction.

    Pour la facilité, nous allons créer une structure bien particulière qui représente "un type inexistant" sous la forme de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    struct NullType{};
    ainsi qu'une liste de type sous la forme de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <typename T, typename U>
    struct TypeList{
        using Head = T;
        using Tail = U;
    };

    Pour la facilité, toujours, nous allons créer un élément qui nous permet de créer rapidement une liste de type sous la forme de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    template <typename ...> struct MakeTypeList;
    template <>struct MakeTypeList<>{
        using Result = NullType;
    };
    template <typename Head, typename ... Types>
    struct MakeTypeList<Head, Types ...>{
        using Result = TypeList<Head, typename MakeTypeList<Types ... >::Result>;
    };

    Bien que nous n'en ayons pas forcément besoin dans le cas présent, il sera toujours intéressant de connaître le nombre de type que contient une liste de type.

    Nous pourrons le savoir à l'aide d'une structure proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    template <typename >
    struct Length;
    template <>
    struct Length<NullType>{
        enum {value = 0};
    };
     
    template <typename Head, typename Tail>
    struct Length<TypeList<Head, Tail>> {
        enum { value = Length<Tail>::value + 1 };
    };

    Enfin, nous aurons besoin de savoir si un type bien particulier fait partie d'une liste de type bien particulière. Pour ce faire, nous allons créer une dernière structure proche de
    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
    template <typename, typename> struct IndexOf;
     
    template <typename T>
    struct IndexOf<NullType, T> {
        enum { value = -1 };
    };
     
    template <typename T, typename Tail>
    struct IndexOf<TypeList<T, Tail>, T> {
        enum { value = 0 };
    };
     
    template <typename Head, typename Tail, typename T>
    struct IndexOf<TypeList<Head, Tail>, T> {
        using Result = IndexOf<Tail, T>;
        enum { value = Result::value == -1 ? -1 : Result::value + 1 };
    };


    Une fois que nous avons tout cela, nous pouvons nous inquiéter de notre problème réel, qui est : être en mesure de déterminer si un type de donnée expose une fonction bien particulière.

    Pour démontrer la faisabilité de la chose, je vais réutiliser trois structures simples prenant la forme de
    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
    struct A{
        void foo(){
            std::cout<<"in foo\n";
        };
    };
    struct B{
        void bar(){
            std::cout<<"in bar\n";
        };
    };
    struct C{
        void foo(){
            std::cout<<"in foo\n";
        };
        void bar(){
            std::cout<<"in bar\n";
        };
    };
    Comme tu le remarque, les structures A et C exposent toutes les deux une fonction foo, et les structures B et C exposent toutes les deux une fonction bar.

    Je vais donc "tout simplement" créer une liste de type (FooList) qui contient "tous les types exposant la fonction foo" (autrement dit, les types A et C) et une autre liste de type (BarList) qui contient "tous les types exposant la fonction bar")(autrement dit, les type B et [c]C[c]) sous la forme de simple alias de type proches de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
        using FooList = MakeTypeList<A, C>::Result;
        using BarList = MakeTypeList<B, C>::Result;


    Il faudra juste être particulièrement attentif à ne pas se tromper en créant ces listes, car si l'on fournit à ces listes un type qui n'expose pas la fonction attendue, nous risquons de nous retrouver avec un message d'erreur tout à fait imbitable, à cause de l'utilisation de template

    Une fois que ces listes sont définies, nous pouvons utiliser la structure IndexOf pour déterminer si un type particuier (correspondant au type d'un paramètre de fonction, par exemple) fait partie d'une liste de type bien particulière, et nous n'avons donc plus que le choix de la manière dont nous voulons nous y prendre pour effectivement faire appel à une fonction bien précise en fonction du résultat obtenu.

    Nous pouvons, par exemple, travailler avec des politiques "classiques", telles que l'on en crée depuis que le C++ autorise le paradigme générique, à savoir quelque chose qui serait proche de
    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
     // la politique "de base, valide pour tout indice valide dans la liste
    template <int>
    struct FooCallPoilcy{
        template <typename T>
        static void call(T & t){
            t.foo();
        }
    };
    // une spécialisation de la politique pour l'indice invalide dans la liste
    template <>
    struct FooCallPoilcy<-1>{
        template <typename T>
        static void call(T & t){
            //just does nothing
        }
    };

    Ou bien, nous pouvons décider d'utiliser l'indice dans la liste avec std::enable_if sous une forme "plus moderne" (je te laisse seul juge quant à savoir si elle est plus facile à comprendre ) proche de
    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, typename BarList>
    struct BarCallPolicy{
        template <typename U=T>
        static void call(U & u, 
                         typename std::enable_if<IndexOf<BarList, U>::value !=-1, int>::type * =nullptr){
            u.bar();
        }
        template <typename U = T>
        static void call(U & u, 
                         typename std::enable_if<IndexOf<BarList, U>::value ==-1, int>::type * =nullptr){
            //just does nothing
        }
    };


    Une fois ces politiques mises en place, il ne nous restera plus qu'à créer une fonction qui fera appel aux politiques adéquates. Dans le cas présent, je conseillerais sans doute une fonction libre template proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template <typename T>
    inline void makeCalls(std::string const & data, T & t){
        std::cout<<"using "<<data<<" as data:\n";
        FooCallPoilcy<IndexOf<FooList, T>::value>::call(t);
        BarCallPolicy<T, BarList>::call(t);
    }
    qui pourrait être utilisée sous une forme proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    int main() {
        A a;
        B b;
        C c;
        makeCalls("a",a);
        makeCalls("b",b);
        makeCalls("c",c);
    }
    et dont la sortie nous donnera un résultat proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    $ ./a.out 
    using a as data:
    in foo
    using b as data:
    in bar
    using c as data:
    in foo
    in bar
    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

  6. #6
    Membre régulier
    Bonjour,

    et merci pour vos réponses!
    @Grool : Précisément, la première déclaration est la plus générale. Elle accepte tous les types, y compris ceux qui n'ont pas la fonction. Mon besoin est de différencier le comportement entre les classes entre celles qui n'ont pas la fonction et celles qui l'ont, sans que ce soit une erreur (en gros, lorsqu'elle existe la fonction doit être utilisée, sinon on fait autrement).
    @jo_link_noir : Merci pour ta réponse, je pense que c'est ce que je cherchais.
    @koala01 : Merci à toi pour ta réponse! Je suis impressionné, perso j'ai mis 30min à poser ma question, ça a du te prendre un temps énorme! Je vais étudier tous les cas que tu proposes.

    Question résolue de mon point de vue.
    Merci encore!

  7. #7
    Membre régulier
    Bonjour
    Oui, je m'aperçois que j'ai moi aussi encore beaucoup à apprendre sur la programmation générique, en particulier sur les type_traits et tout ce que le C++11 a apporté.

    Après (mais c'est peut-être une question de goût ou parce que je ne connais pas encore assez le sujet), je trouve que cela donne une syntaxe assez lourde et difficile à comprendre.

  8. #8
    Expert éminent sénior
    Citation Envoyé par Teaniel Voir le message
    ça a du te prendre un temps énorme!
    Ben, en gros, deux heures pour écrire le code avec les listes de type, tester les différentes utilisations et écrire la réponse, vu que ma première intervention a été postée à 20:53 et la deuxième à 22:52

    EDIT:

    Note, d'ailleurs, qu'une fois que l'on a la possibilité de créer des listes de types, on peut s'amuser pas mal, par exemple, en créant un type trait qui vérifie la présence d'un type donné dans deux liste, sous une forme proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template <typename T,
              typename FIRST = FooList, // pour le cas ou l'on voudrait changer la liste
              typename SECOND = BarList> // idem
    struct TypeListTrait{
        static constexpr bool inFirstList = IndexOf<FIRST,T>::value != -1;
        static constexpr bool inSecondList = IndexOf<SECOND,T>::value != -1;
    };
    que l'on pourrait utiliser pour créer une politique proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <typename T,
              typename TRAIT = TypeListTrait<T>,
              bool inFirstList = TRAIT::inFirstList,
              bool inSecondList = TRAIT::inSecondList>
    struct FooBarCaller;

    Politique que nous spécialiserions partiellement pour les différents cas qui s'offrent à nous, à savoir:
    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
    // le type se trouve dans la première liste uniquement
    template <typename T>
    struct FooBarCaller<T, TypeListTrait<T>, true, false>{
        static void call(T & t){
            std::cout<<"has just foo\n";
            // on ne fait donc appel qu'à foo
            t.foo();
        }
    };
    // le type se trouve dans la deuxième liste seulment
    template <typename T>
    struct FooBarCaller<T, TypeListTrait<T>, false, true>{
        static void call(T & t){
            std::cout<<"has just bar\n";
           // on ne fait donc appel qu'à bar
            t.bar();
        }
    };
    // le type se trouve dans les deux liste
    template <typename T>
    struct FooBarCaller<T, TypeListTrait<T>, true, true>{
        static void call(T & t){
            std::cout<<"has foo and bar\n";
           // on fait donc appel à foo ET à bar
            t.foo();
            t.bar();
        }
    };
    // le type ne se trouve dans aucune liste
    template <typename T>
    struct FooBarCaller<T, TypeListTrait<T>, false, false>{
        static void call(T & t){
            std::cout<<"has nothing\n";
            // on ne fait donc appel à rien
        }
    };
    Pour confirmer que la quatrième possibilité (le type est absent des deux listes) fonctionne, nous pouvons créer une dernière structure proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    struct D{
        //pour montrer qu'il y a quelque chose qui n'est ni la fonction foo, ni la fonction bar <img src="images/smilies/icon_biggrin.gif" border="0" alt="" title=":D" class="inlineimg" />
        void doSomething(){
            std::cout<<"in doSomething\n";
        }
    };
    et, bien sur, corriger notre fonction makeCalls qui automatise la sélection de la politique adéquate pour lui donner la forme de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <typename T>
    inline void makeCalls(std::string const & data, T & t){
        std::cout<<"using "<<data<<" as data:\n";
        FooBarCaller<T>::call(t);
    }

    et, enfin, nous corrigeons également la fonction main pour qu'elle prenne le type D en compte, ce qui lui donne la forme de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int main() {
        A a;
        B b;
        C c;
        D d;
        makeCalls("a",a);
        makeCalls("b",b);
        makeCalls("c",c);
        makeCalls("d",d);
    }
    et qui, une fois compilé nous fournira une sortie proche de
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $ ./a.out 
    using a as data:
    has just foo
    in foo
    using b as data:
    has just bar
    in bar
    using c as data:
    has foo and bar
    in foo
    in bar
    using d as data:
    has nothing
    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

  9. #9
    Membre expert
    pour IndexOf / TypeList et les type varidiques en général, il faut au maximum éviter la récursivité car:

    - le code est plus verbeux avec une ou plusieurs spécialisations.
    - c'est lent à compiler et prend plus de mémoire (toujours à la compilation).
    - il n'est pas possible de faire des folds.

    De moins point de vue, il y a 4 types d'algorithme:

    - Les algorithmes de transformation type std::transform: il n'y a jamais besoin de récursivité.
    - Les algorithmes de réduction comme std::accumulate, c'est "facile" avec un fold.
    - Les algorithmes d'expansion (génération de n élément par exemple), on peut grandement réduire la récursivité, mais il y a toujours besoin
    - Les algorithmes avec condition d'arrêt tel que index_of, et là, c'est au cas par cas.

    Pour le cas spécifique d'index_of, j'ai fait un article qui mesure l'impact de différente implémentation au niveau du compilateur. L'avant-dernière est très lisible et efficace.

    Sinon dans le cas général, je conseille d'utiliser des bibliothèques de méta-programmation qui sont efficaces, testées et très optimisées. Pour démarrer je conseille metal, voir boost.hana qui sont toutes les 2 très documentées.

    Il y a un lien vers un livre de méta-programmation sur le dépôt de brigand.

  10. #10
    Expert éminent sénior
    A vrai dire, mon intention était surtout d'expliquer le concept des types de listes, et de montrer plusieurs manières de l'utiliser pour résoudre un problème donné.

    J'avoue avoir fait au "plus simple", sans m'inquiéter du problème des performances à la compilation

    D'ailleurs, le simple fait que j'ai utilisé IndexOf au lieu d'une fonctionnalité qui se contente de vérifier si un type donné est présent dans la liste de type (comme TypeListContains) permettant d'obtenir directement un booléen démontre que je n'avais pas trop réfléchi à ce dont j'avais besoin pour pouvoir l'utiliser

    Mais tu as tout à fait raison ceci dit : la programmation générique est géniale, parce qu'elle nous permet de faire travailler le compilateur à notre place, mais tout ce que l'on peut lui demander de faire à notre place a forcément un coup en temps de compilation et en termes d'utilisation de la mémoire lors de celle-ci.

    Pour de petits exemples simples comme ceux que j'ai donnés, nous ne serons sans doute pas confrontés à ces problèmes, mais il faut effectivement bien être conscient que le problème risque d'arriver très rapidement sur des projets réels, plus complexes

    Enfin, je ne peux bien sur qu'être tout à fait d'accord avec toi : il est toujours préférable d'utiliser une bibliothèque tierce, d'utilisation largement répandue, à une implémentation personnelle forcément imparfaite
    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

###raw>template_hook.ano_emploi###