Meta-Programmation Template : une introduction ....

Meta-Programmation Template : une introduction .... - C++ - Programmation

Marsh Posté le 28-07-2003 à 22:37:25    

Bon voila, j'ai décidé d'un commun accord avec Taz de pondre quelques topics sur le C++ , le vrai, celui qui fera de vous un homme :D
 
Je vais entamer ici une série de post sur la métaprogrammation template.  
 
PRINCIPES :  
 
La metaprogramamtion template est un ensemble de technique utilisant les mécanismes du template pour générer du code optimisé, au moment de la compilation, induisant un gain de vitesse non négligeable à l'éxécution.  
 
Quel intéret ? Et bien, la possibilité d'écrire du code sécurisé et permettant facilement de générer des constantes, des boucles déroulées et plein d'autres astuches.
 
La grande idée c'est comme dit Taz : "Think you're a compiler" !
Comment ce code peut il etre reecrit afin de mettre en evidence tel ou tel comportement template ? Est-ce que ce problème n'est pas simplement resoluble par une grammaire ??
 
1. Rappels
 
Quelques faits (mé)connus des templates :
 
1. Les classes et fonction templates sont instanciées en place ne produisant pas ou peu d'appel de fonction.
Le code template est quasiment toujours déroulé par le compilateur, un inline automatique.
 
2. Les arguments des templates ne sont pas nécessairement des types. Un template peut recevoir des types entiers (char,short,int,bool) en paramétres :
 

Code :
  1. template<int V> struct Constante
  2. {
  3.   static const int value = V;
  4. }
  5. // affiche '17'.
  6. cout << Constante<17>::value << endl;

 
 
On peut aussi feinter pour utiliser ds double par exemple :
 

Code :
  1. template<class T,T V> struct Constante
  2. {
  3.   static const T value = V;
  4. }
  5. // affiche '17.3'.
  6. cout << Constante<double,17.3>::value << endl;


 
3. Une fonction template peut determiner automatiquement ces paramétres de type.
 

Code :
  1. template<class T> T max( const T& a, const T& b )
  2. {
  3.   return ( a<b ? b : a );
  4. }

 
 
A partir de la, que peut on faire ???
 
2. La factorielle
Considérons maintenant un code ou il est nécessaire d'utiliser de nombreuses factorielles.Disons, un calcul de développement limité. Etant des constantes, ce genre de valeur sont souvent  
stockés dans des enum ou pire des #define !!!
Nos chers templates reviennent à la rescousse :
 

Code :
  1. template<int V> struct factorielle
  2. {
  3.   enum { value=V*factorielle<V-1> };
  4. };
  5. template<> struct factorielle<0>
  6. {
  7.   enum { value=1 };
  8. };


 
Maintenant, nous voulons évaluez 11! :
 

Code :
  1. int fact_11 = factorielle<11>::value;


 
Ce code enclenche une série d'évaluation :
 

Code :
  1. int fact_11 = 11*factorielle<10>::value;
  2. int fact_11 = 11*10*factorielle<9>::value;
  3. ...
  4. int fact_11 = 11*10*9*...*2*1 = 39916800;


 
Evidemment l'ensemble de ce code est produit et évalué à la compilation.
 
3. La Boucle FOR
Considérons ce code :
 

Code :
  1. for(int i=0;i<100000;i++) tab[i] = 2*i+1;

 
 
Il va générer 100000 tests de bornes, retour et affectation.
On peut gagner un peu en écrivant quelques choses comme :
 

Code :
  1. for(int i=0;i<50000;i++)
  2. {
  3.   tab[2*i] = 2*(2*i)+1;
  4.   tab[2*i+1] = 2*(2*i+1)+1;
  5. }

 
On gagne 50000 tests/retour.
L'ultime abus serait de dérouler ENTIEREMENT cette boucle, éliminant ainsi tous test et sauts. Bon, les maso léve la main et vont coder ca a la main :sol:
 
Comment faire ?
On va utiliser un aspect un peu méconnu du template : le fait que son ou ses arguments ne sont pas forcement des types mais peuvent etre des entiers. Considérons cette fonction template :
 

Code :
  1. // fonction de base
  2. template<int N> void remplir( int* tab )
  3. {
  4.    tab[N] = 2*N+1;
  5.    remplir<N-1>(tab);
  6. }
  7. // fonction de terminaison
  8. template<> void remplir<0>( int* tab )
  9. {
  10.    tab[0] = 1;
  11. }

 
 
Si nous l'appellons ainsi :
 

Code :
  1. remplir<99>(tab);


 
Le code suivant est produit à la compilation :  

Code :
  1. tab[99] = 199;
  2. tab[98] = 197;
  3. tab[97] = 195;
  4. ...
  5. tab[2] = 5;
  6. tab[1] = 3;
  7. tab[0] = 1;


 
Gain de vitesse notable (proche du x2 ou du x3) mas evidemment perte sur la taille de l'executable.
Par contre , il faut faire gaffe au paramétres. g++ ne deplie les templates que sur 512 niveau. Une option permet d'augmenter cette valeur, mais le temps de compilation devient vite infâme.
Un projet perso utilise 2048 comme limite et prends plusieurs minutes sur une seule fonction   :ouch:  
 
4. Un Tri résolu à la compilation
Le tri à bulles est un des tris les plus simples à gérer. Néanmoins, pour des tableaux de grandes tailles,
bah c'est pas trop ça :/. Pourtant, sur des tableaux de petites tailles, il reste le plus intéressant à implanter :
 

Code :
  1. inline void swap(int& a, int& b)
  2. {
  3.     int temp = a;
  4.     a = b;
  5.     b = temp;
  6. }
  7. void sort(int* data, int N)
  8. {
  9.   for (int i = N - 1; i > 0; --i)
  10.   {
  11.      for (int j = 0; j < i; ++j)
  12.      {
  13.         if (data[j] > data[j+1]) swap(data[j], data[j+1]);
  14.      }
  15.   }
  16. }


 
Voila, c'est trés simple. On peut alors se dire, par exemple, de spécialiser
le tri à bulles pour des tableaux de trois éléments :
 

Code :
  1. void bubbleSort(int* data, int N)
  2. {
  3.   for (int j = 0; j < N - 1; ++j)
  4.   {
  5.     if (data[j] > data[j+1]) swap(data[j], data[j+1]);
  6.   }
  7.   if (N > 2) bubbleSort(data, N-1);
  8. }


 
Bon .... maintenant, si nous voulons produire un code équivalent pour une taille voulue, on ne va pas non plus réécrire ce code N fois pour chaque taille. La Meta-Programamtion template arrive à la rescousse. Voila une classe de Tri A Bulles basé sur l'algo ci-dessus :
 

Code :
  1. template<int N> struct IntBubbleSort
  2. {
  3.   static inline void sort(int* data)
  4.   {
  5.     IntBubbleSortLoop<N-1,0>::loop(data);
  6.     IntBubbleSort<N-1>::sort(data);
  7.   }
  8. };
  9. template<> struct IntBubbleSort<1>
  10. {
  11.   static inline void sort(int* data) {}
  12. };


 
L'appel suivant :
 

Code :
  1. IntBubbleSort<4>::sort( int* data);


 
va générer un code semblable à :
 

Code :
  1. IntBubbleSortLoop<3,0>::loop(data);
  2. IntBubbleSortLoop<2,0>::loop(data);
  3. IntBubbleSortLoop<1,0>::loop(data);


 
Bon que dois faire IntBubbleSortLoop<N,M>::loop() ???
Et bien, il va générer les divers tests sur les éléments :
 

Code :
  1. template<int I, int J>
  2. class IntBubbleSortLoop
  3. {
  4.   private:
  5.   enum { go = (J <= I-2) };
  6.   public:
  7.   static inline void loop(int* data)
  8.   {
  9.      IntSwap<J,J+1>::compareAndSwap(data);
  10.      IntBubbleSortLoop<go ? I : 0, go ? (J+1) : 0>::loop(data);
  11.   }
  12. };
  13. class IntBubbleSortLoop<0,0>
  14. {
  15.   public:
  16.   static inline void loop(int*) {}
  17. };


 
Rebelote, voila le code de notre tri :
 

Code :
  1. IntSwap<0,1>::compareAndSwap(data);
  2. IntSwap<1,2>::compareAndSwap(data);
  3. IntSwap<2,3>::compareAndSwap(data);
  4. IntSwap<0,1>::compareAndSwap(data);
  5. IntSwap<1,2>::compareAndSwap(data);
  6. IntSwap<0,1>::compareAndSwap(data);


 
On approche de la solution, il suffit de coder la méthode IntSwap<I,J>::compareAndSwap
qui va effectuer le tri :
 

Code :
  1. template<int I, int J> struct IntSwap
  2. {
  3.   static inline void compareAndSwap(int* data)
  4.   {
  5.     if (data[I] > data[J]) swap(data[I], data[J]);
  6.   }
  7. };


 
swap() n'a pas changé :
 

Code :
  1. inline void swap(int& a, int& b)
  2. {
  3.     int temp = a;
  4.     a = b;
  5.     b = temp;
  6. }


 
Au final , notre appel IntBubbleSort<4>::sort( int* data); devient :  
 

Code :
  1. if (data[0] > data[1]) swap(data[0], data[1]);
  2. if (data[1] > data[2]) swap(data[1], data[2]);
  3. if (data[2] > data[3]) swap(data[2], data[3]);
  4. if (data[0] > data[1]) swap(data[0], data[1]);
  5. if (data[1] > data[2]) swap(data[1], data[2]);
  6. if (data[0] > data[1]) swap(data[0], data[1]);


 
Il suffit alors de changer le paramétre template de IntBubbleSort pour générer des fonctions de tri de taille diverses.
 
Au niveau perf, le tri est 7 à 3 fois plus rapide pour des tableaux de taille inférieure à 10, et le gain se stabilise vers 1.6 pour des tableau plus grand. un gain non négligeable somme toute. On peut aussi rajouter des paramétres templates pour gérer le type des données triées, modifiées la fonction de comparaison par des foncteurs etc ...
 
 
Voila, quelques choses simples. Je posterais une petite explication sur d'autres techniques liées aux templates comme les traits, les expressions templates etc ...
 
 
EDIT : typos, erreur diverse :p


Message édité par Joel F le 29-07-2003 à 09:21:01
Reply

Marsh Posté le 28-07-2003 à 22:37:25   

Reply

Marsh Posté le 28-07-2003 à 22:45:10    

Bravo ! Et bravo pour les exemples concrets. Ça manque souvent quand on veut saisir l'intérêt des templates.
 
/me qui se remet au C++ difficilement :sweat:


Message édité par gm_superstar le 28-07-2003 à 22:45:49

---------------
Incongru : une FAQ abandonnée sur les Standards du Web - FAQ périmée de blabla@Prog
Reply

Marsh Posté le 28-07-2003 à 22:45:28    

http://membres.lycos.fr/cybear/Forum/topicD.gif


Message édité par schnapsmann le 28-07-2003 à 22:47:56

---------------
From now on, you will speak only when spoken to, and the first and last words out of your filthy sewers will be "Sir!"
Reply

Marsh Posté le 28-07-2003 à 22:55:04    

je me permet de rajouter 2/3 trucs et un exemple tres concret pour ceux qui veulent se faire les dents.
 
 
1) je ferais un topic sur le mot clef typename, pour le moment retenez en le même usage que class pour les templates
2) JoelF utilise des enums, moi des constantes, les 2 choix sont valides suivant l'utilisation
 
Comment déterminer au moment de la compilation si 2 types sont équivalents?
 

Code :
  1. template<typename T1, typename T2>
  2. struct SAME_TYPE
  3. {
  4.   static const bool RESULT=false;
  5. };
  6. template<typename T>
  7. struct SAME_TYPE<T, T>
  8. {
  9.   static const bool RESULT=true;
  10. };


 

Code :
  1. SAME_TYPE<int, char>::RESULT;


 
 
Maitenant, une petite réflexion: quand on construit des classes, ont les fait souvent reposer sur des classes d'implémentation dont elles heritent, pour séparer implémentation brutale et interface. Ce qu'on aimerait faire, c'est un classe qui en fonction de son paramètre template hérite de la base qui va bien. Le code sans plus attendre
 

Code :
  1. // template IF statement
  2. template <bool c, typename TRUE, typename FALSE>
  3. struct IF;
  4. template <typename TRUE, typename FALSE>
  5. struct IF <true, TRUE, FALSE>
  6. {
  7.   typedef TRUE RESULT;
  8. };
  9. template <typename TRUE, typename FALSE>
  10. struct IF <false, TRUE, FALSE>
  11. {
  12.   typedef FALSE RESULT;
  13. };
  14. #include <iostream>
  15. using namespace std;
  16. template<class T>
  17. struct BigBar
  18. {
  19.   T item;
  20.  
  21.   void say_hello() const
  22.   {
  23.     cout << "BigBar\n";
  24.   }
  25. };
  26. template<class T>
  27. struct SmallBar
  28. {
  29.   T item;
  30.  
  31.   void say_hello() const
  32.   {
  33.     cout << "SmallBar\n";
  34.   }
  35. };
  36. template<class T>
  37. class Foo :
  38. private IF < sizeof(T) <= sizeof(int),
  39.      SmallBar<T>,
  40.      BigBar<T>
  41.            > :: RESULT
  42.   {
  43.   public:
  44.     void operator()() const
  45.       {
  46. say_hello();
  47.       }
  48.   };
  49. int main()
  50. {
  51.   Foo<int>()();
  52.   Foo<char>()();
  53.   Foo<double>()();
  54. }


 
 
vous allez me dire: tout ça ne sert pas à grand chose. Voici un exemple tres concret: une classe qui implémente un tableau de taille fixe. En fonction de son occupation mémoire, il doit résider soit dans la pile soit dans le tas. On veut que les 2 implémentations soient compatibles, et que tout ce qui le peut soit optimiser (comme la permutation de 2 tableaux, tres rapides dans le cas de pointeur)
voilà la classe et un petit programme de test
http://dejean.benoit.free.fr/code/Array.hpp
http://dejean.benoit.free.fr/code/test_Array.cpp
 
c'est un petit exemple sans prétention, non exempt de bugs, testé sommairement, mais fonctionnel quand je m'en étais servis. compatible avec STL
 

Reply

Marsh Posté le 28-07-2003 à 22:59:02    

C'est une super idée votre truc !! Bravo !!  :jap:  
 
Cela dit je n'ai pas trop envi de devenir un homme ... :whistle:


---------------
http://membres.lycos.fr/axelfa
Reply

Marsh Posté le 28-07-2003 à 23:00:16    

Tchoupinette a écrit :

C'est une super idée votre truc !! Bravo !!  :jap:  
 
Cela dit je n'ai pas trop envi de devenir un homme ... :whistle:  

moi non plus

Reply

Marsh Posté le 28-07-2003 à 23:03:02    

Arf,  
 
"le C++, pour nous les hommes"
 
Ah ben non pas super le slogan  [:taimp]

Reply

Marsh Posté le 28-07-2003 à 23:05:04    

petite précision: la métaprogrammation de template repose sur les mécanismes de spécialisation de template, donc je vous invite à travailler ça. ce qui explique que  
 

Code :
  1. template <bool c, typename TRUE, typename FALSE>
  2. struct IF;
  3. template <typename TRUE, typename FALSE>
  4. struct IF <true, TRUE, FALSE>
  5. {
  6.   typedef TRUE RESULT;
  7. };
  8. template <typename TRUE, typename FALSE>
  9. struct IF <false, TRUE, FALSE>
  10. {
  11.   typedef FALSE RESULT;
  12. };


 
équivaut à  
 

Code :
  1. template <bool c, typename TRUE, typename FALSE>
  2. struct IF
  3. {
  4.   typedef TRUE RESULT;
  5. };
  6. template <typename TRUE, typename FALSE>
  7. struct IF <false, TRUE, FALSE>
  8. {
  9.   typedef FALSE RESULT;
  10. };


 
et faites attention car les templates supportent aussi les paramètres par défaut!

Reply

Marsh Posté le 28-07-2003 à 23:06:23    

Il faut aussi savoir que certains compilateurs ne supportent pas toutes cs acrobaties ... gcc et cie sont en général bien fiable, mais certains autres le sont moins :/
 
@Taz : Et quand est il de la spécialisation PARTIELLE d'un tempalte ? J'ai toujours du mal avec ca ....

Reply

Marsh Posté le 28-07-2003 à 23:08:02    

Joel F a écrit :

Il faut aussi savoir que certains compilateurs ne supportent pas toutes cs acrobaties ... gcc et cie sont en général bien fiable, mais certains autres le sont moins :/
 
@Taz : Et quand est il de la spécialisation PARTIELLE d'un tempalte ? J'ai toujours du mal avec ca ....

des que je trouve un peu de temps, je fais un topic sur les differents types de spécialisation

Reply

Marsh Posté le 28-07-2003 à 23:08:02   

Reply

Marsh Posté le 29-07-2003 à 08:19:22    

bonjour,
 
je me permet de rajouter ce lien, qui m'avait initier à ce type de programmation pour ceux qui veulent aller voir:
http://osl.iu.edu/~tveldhui/papers [...] a-art.html
 
Comme il est dit:
"This article first appeared in the May 1995 issue of C++ Report. It has been reprinted in the book "C++ Gems" edited by Stanley Lippman."

Reply

Marsh Posté le 29-07-2003 à 08:29:00    

flag!
Vous vous déchainez sur le C++ !!
:jap:

Reply

Marsh Posté le 29-07-2003 à 08:40:55    

skeye a écrit :

flag!
Vous vous déchainez sur le C++ !!
:jap:


 drapo [:texla]

Reply

Marsh Posté le 29-07-2003 à 09:06:50    

:hello: Bravo Joel, c'était très clair !
 
(même si moi non plus, je ne veux pas devenir un homme :lol:)

Reply

Marsh Posté le 29-07-2003 à 09:10:13    

Très très intéressant ! C'est cependant le type de code que j'hésite à écrire parce que je n'y pense pas, que je n'ai pas suffisamment d'expérience pour savoir quand l'utiliser, et qui peut rapidement jouer des pièges si on ne connait pas bien son compilateur (je pense notamment à la limite du nombre de niveaux de récursion supporté par un compilateur).
Le fait de travailler sous Visual C++ 6.0 n'invite pas non plus à pousser beaucoup les expériences (j'ai déjà passé un peu de temps sur du code qui me semblait devoir marcher mais ne fonctionnait pas parce que Visual C++ 6.0 ne respecte pas bien le Koenig Lookup).
 
Continuez ce genre d'articles qui sont très intéressants ! J'en profite pour orienter vers les C++ Tips and Tricks qui contiennent plein d'infos intéressantes.
 
Par contre, je pense qu'il y a une erreur ici :

Joel F a écrit :


...

Code :
  1. // fonction de base
  2. template<int N> remplir( int* tab )
  3. {
  4.    tab[N] = 2*N+1;
  5.    remplir<N-1>(tab);
  6. }
  7. // fonction de terminaison
  8. template<int N> remplir( int* tab )
  9. {
  10.    tab[0] = 1;
  11. }


...


 
La fonction de terminaison est plutôt :

Code :
  1. template<0> remplir( int* tab )
  2. {
  3. // ...
  4. }


il me semble ?


---------------
each day I don't die is cheating
Reply

Marsh Posté le 29-07-2003 à 09:17:30    

Code :
  1. // fonction de terminaison  
  2. template<> remplir<0>( int* tab )
  3. {
  4.   tab[0] = 1;
  5. }

 
 
Plus precisement, il faut bien remplir l'indice 0 du tabelau ...
Je corrige asap

Reply

Marsh Posté le 29-07-2003 à 09:19:05    

Oui, et donne aussi un type à la fonction sinon ça ne va toujours pas marcher :
 

Code :
  1. // fonction de terminaison   
  2. template<> void remplir<0>( int* tab ) 
  3.    tab[0] = 1; 
  4. }


Message édité par umag le 29-07-2003 à 09:19:46
Reply

Marsh Posté le 29-07-2003 à 09:31:10    

Correction fait [:prosterne]

Reply

Marsh Posté le 29-07-2003 à 12:57:23    

je complète l'exemple de Taz sur le template If pour les compilateurs sans spécialisation partielle. C'est pas toujours possible de se passer de la spécialisation partielle, mais là c'est bon. A l'utilisation c'est pareil
 
 

Code :
  1. // Les deux types suivants "simulent" des template-typedef
  2. // Sélectionne toujours le premier type
  3. struct SelectFirstType
  4. {
  5.    template<typename Tp1, typename Tp2>
  6.    struct TypeSelector
  7.    {
  8.       typedef Tp1 Type;
  9.    };
  10. };
  11. // Sélectionne toujours le second type
  12. struct SelectSecondType
  13. {
  14.    template<typename Tp1, typename Tp2>
  15.    struct TypeSelector
  16.    {
  17.       typedef Tp2 Type;
  18.    };
  19. };
  20. // On choisit le premier sélecteur
  21. template<bool condition>
  22. struct SelectSelector
  23. {
  24.    typedef SelectFirstType Selector;
  25. };
  26. // Spécialisation pour false, en choisissant le second type
  27. template<>
  28. struct SelectSelector<false>
  29. {
  30.    typedef SelectSecondType Selector;
  31. };
  32. // Selecteur de type
  33. template<bool condition, typename Tp1, typename Tp2>
  34. struct If
  35. {
  36.    // On sélectionne le selecteur
  37.    typedef typename SelectSelector<condition>::Selector Selector;
  38.    // puis on l'utilise
  39.    typedef typename Selector::TypeSelector<Tp1, Tp2>::Type Type;
  40. };

Reply

Marsh Posté le 02-08-2003 à 03:45:37    

détail technique : la "fonction de terminaison" s'appelle "le point fixe" (et n'est pas toujours une fonction comme montré dans le premier exemple).
Tiens d'ailleur ils disent quoi vos différents compilos si vous en mettez pas, il gueule a l'instanciation ou á la compilation du template, il pete la pile ou il s'est méfié avant ?


---------------
trainoo.com, c'est fini
Reply

Marsh Posté le 02-08-2003 à 11:44:28    

le mien il pète apres m'avoir afficher un message pour chaque instanciation...

Reply

Marsh Posté le 02-08-2003 à 12:01:07    

nraynaud a écrit :

détail technique : la "fonction de terminaison" s'appelle "le point fixe" (et n'est pas toujours une fonction comme montré dans le premier exemple).
Tiens d'ailleur ils disent quoi vos différents compilos si vous en mettez pas, il gueule a l'instanciation ou á la compilation du template, il pete la pile ou il s'est méfié avant ?


 
Je pense que même les meilleurs compilos c++ n'ont pas l'intelligence nécéssaire pour détecter ce genre de choses : ils se content d'un profondeur maximale de "templetisation" à ne pas dépasser.
 
J'ajouterais que ce genre de choses est assez difficile à détecter: dans le cas général, cela revient grosso modo a décider de la terminaison d'un programme, ce qui est un problème NP complet.

Reply

Marsh Posté le 03-08-2003 à 05:31:48    

SchnapsMann a écrit :

J'ajouterais que ce genre de choses est assez difficile à détecter: dans le cas général, cela revient grosso modo a décider de la terminaison d'un programme, ce qui est un problème NP complet.

Non, justement, ce cas précis (absence de point fixe dans une expression récursive) est assez simple, il donne un joli _|_ (bottom) quand tu tentes d'inférer son type.
Ça peut parraître bizzare de tenter de typer un type, mais en réalité ça se fait courament dans certains domaines. En prenenant le langage des templates comme un langage de script qui te génère du code C amélioré, c'est assez facile de faire le lien avec le domaine fonctionel (réécriture de graphes et pattern-matching sur des valeurs).
 
edit : si un jour je me motive, je ferais la démo de programmes fonctionnels en template C++ et en caml.


Message édité par nraynaud le 03-08-2003 à 05:40:01
Reply

Marsh Posté le 04-02-2004 à 16:06:33    

schnapsmann a écrit :


 
Je pense que même les meilleurs compilos c++ n'ont pas l'intelligence nécéssaire pour détecter ce genre de choses : ils se content d'un profondeur maximale de "templetisation" à ne pas dépasser.
 
J'ajouterais que ce genre de choses est assez difficile à détecter: dans le cas général, cela revient grosso modo a décider de la terminaison d'un programme, ce qui est un problème NP complet.


J'aimerais bien. En vrai, c'est indécidable (mais c'est sans doute ce que tu voulais dire) ;)

Reply

Marsh Posté le 21-03-2004 à 12:25:42    

supprimez ces 2 messages svp

Reply

Marsh Posté le 21-03-2004 à 12:55:43    

bon a quoi sert le struct alors ?
je pose une question maintenant si ma question est completement stupide ... j irais voir ailleur.

Reply

Marsh Posté le 21-03-2004 à 12:59:24    

elle est stupide

Reply

Marsh Posté le 21-03-2004 à 13:00:00    

Le struct définit un type et un espage de nommage.

Reply

Marsh Posté le 21-03-2004 à 13:08:00    

je ne savais pas comment creer son namespace, j ai apris quelque chose. donc ma question netait pas inutile.
merci.

Reply

Marsh Posté le 21-03-2004 à 13:09:56    

xiluoc a écrit :

je ne savais pas comment creer son namespace, j ai apris quelque chose. donc ma question netait pas inutile.
merci.  

les struct/class n'ont rien à voir avec les "namespace"
 
 
et en bon français, on dit pas "espace de nommage" on dit "référentiel lexical"

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed