Je viens de faire un constat contrariant.Code:
1
2
3 #define N sizeof(long) #if N == 8 ...
Le premier code est refusé, le second accepté 8OCode:
1
2
3 #define N 4 #if N == 8 ...
Pourquoi qu'il aime pas les sizeof() ? :calim2:
Version imprimable
Je viens de faire un constat contrariant.Code:
1
2
3 #define N sizeof(long) #if N == 8 ...
Le premier code est refusé, le second accepté 8OCode:
1
2
3 #define N 4 #if N == 8 ...
Pourquoi qu'il aime pas les sizeof() ? :calim2:
Parce que sizeof n'est pas géré par le préprocesseur, mais par le compilateur. Et le préprocesseur refuse de comparer des chaînes et des entiers (je ne suis même pas sûr qu'il sache comparer des chaînes, j'ai souvenir que non).
Oui, ça se tient. Logique quelque part.
Mais bon, ce ne serait pas du luxe si le preprocesseur pouvait évaluer les sizeof().
Merci.
Salut,
Il faut aussi comprendre dans quel ordre tout s'effectue, lorsque tu lance une compilation, entre le code source que tu as écrit et le binaire exécutable que tu obtiens...
Pour faire simple (et donc, de manière pas tout à fait juste), on peut dire qu'il y a cinq étapes distinctes:
Bon, s'il faut être honnête, les compilateurs actuels ne sont plus obligé de passer par une étape en code assembleur, mais ils sont toujours capables de fournir un tel code :D, et l'éditeur de liens n'intervient que pour les liens internes d'un projet, à condition que ce ne soit pas pour faire une bibliothèque statique...
- Les commentaires sont supprimé du code
- Le préprocesseur passe le premier, et gère ses propres directives (#ifdef, #define, inlcude, ...)
- Le compilateur converti ce qui reste en code assembleur
- L'assembleur génère les instructions processeur (binaire objet)
- L'éditeur de liens relie les fichiers binaires objets de manière à en faire un fichier unique cohérent
Par contre, les premiers compilateurs suivaient exactement ce genre de séquence :D
Et le fait est que l'opérateur sizeof est pris en compte... au moment de créer le code assembleur (lors de l'étape 3, en sommes)...
C'est à dire, bien après que le préprocesseur soit passé par là ;)
Par contre, l'opérateur sizeof est une constante de compilation, ce qui fait que la valeur obtenue grace à lui peut parfaitement être interprétée par le compilateur au moment de la compilation, et non au moment de l'exécution...
Lorsque l'on sait que l'opérateur ternaire ? (qui peut être utilisé pour créer un test sous la forme de <expression constante> ? <faire si Vrai> : <faire si faux> ) peut, lui aussi, être interprété au moment de la compilation, il est tout à fait possible d'envisager de l'utiliser (les utiliser) lorsque l'on travaille avec les templates:
fera que la valeur obtenue parCode:
1
2
3
4
5 template <typename T, typename U> struct unTestQuelconque { enum {value = (sizeof(T)<sizeof(U) ) ? -1 : 1} ; };
sera évaluée, au moment de la compilation à -1 alors que la valeur obtenue parCode:unTestQuelconque<char, long long>::value
sera évaluée à 1 (au moment de la compilation, toujours)Code:unTestQuelconque<long long, char>::value
Intéressant.
Donc ce genre de code est "incorrect" ?Le typedef n'est défini qu'à la compilation, le preprocesseur ne verra jamais la définition de int16.Code:
1
2
3 #ifndef int16 typedef __int16 int16; #endif
D'ordre plus général, les types n'existent pas pour le preprocesseur donc je suppose qu'il est inutile de faire quelque chose comme ceci:C'est alors un peu compliqué de faire du code portable. Il faut connaitre à l'avance quels sont les types reconnus par le compilateur.Code:
1
2#ifdef wchar_t ...
Etant donné, que, quand même et malgré tout, preprocesseur et compilateur agissent de concert pour "lire" du code source, je ne verrais pas d'objection à ce qu'une extension soit faite aux preprocesseurs afin qu'ils connaissent les types primitifs du compilateur associé, de même que certaines "fonctions" qui y sont liées (comme sizeof() ou typeid()).
On ne prévoirait pas ça pour la norme C0x++ ? :D
D'un autre côté, en C++, c'est moins un problème qu'en C, vu qu'on peut utiliser la métaprogrammation pour avoir des ifs statiques quand même:
Malheureusement, comme l'indique mon commentaire, ça ne permet pas d'utiliser quelque chose qui ne compile pas quand la condition n'est pas satisfaite...Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 #include <iostream> template< bool b > void UneFonctionSizeof(void); template<> void UneFonctionSizeof<true>(void) { std::cout << "la condition sur sizeof est vraie" << std::endl; } template<> void UneFonctionSizeof<false>(void) { std::cout << "la condition sur sizeof est fausse" << std::endl; //UnTypeNonDeclare toto; mince, ceci ne marche pas. //toto.Test(); } void TestMetaSizeof(void) { UneFonctionSizeof(sizeof(short int) == 2); }
Ainsi donc, impossible de savoir si par exemple le type "long long" est supporté, ni par preproc ni par metaprog.
Ou si "__int64" est identique à "long long" (c'est le cas en VS2005, il n'existe pas en MSVC6).
Ou si "__int32" est identique à "int" (c'est le cas en VS2005, ce ne l'est pas en MSVC6).
Que se sont des types identiques.VS2005 n'accepte pas le code qui précède (MSVC6 l'accepte), pas plus que le suivant.Code:
1
2 void foo(int i) {} void foo(__int32 i) {}
Ce sont les mêmes prototypes de fonctions.Code:
1
2 void foo(long long i) {} void foo(__int64 i) {}
En bref, difficile de dire quels types existent et/ou sont identiques pour un compilo donné.
D'un autre côté, sizeof ne permet pas non plus de le savoir, ça.
Au delà du sizeof(), quelles autres astuces peut-on utiliser en métaprogramation ?
Ainsi avec sizeof() on peut connaître et comparer la taille d'un type.
De même, peut-on savoir si un type est primitif ou "complexe" ? (i.e. "int" est primitif, "struct S {};" est complexe)
Peut-on savoir si un type (struct ou class) possède des méthodes virtuel ?
Peut-on savoir si un type est une référence ou un pointeur ? (avec leur variante const ou pas)
Peut-on savoir si un type est signé ou non ?
Peut-on savoir si un type est const ou non ?
J'ai par exemple essayé ceci qui ne compile pas:Code:
1
2
3
4
5
6
7
8
9
10
11 template<I> void foo(I i) { //coment savoir si I est signé ou non ? unsigned I ui=i; } main() { foo<int>(25); }
Salut,
En général, on utilise des spécialisations de structure template :
Et là encore, c'est une version pas tout à fait correcte car elle ne gère pas les références/pointeurs et mille et autres petits détails du langage ou des compilateurs.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 template<class T> struct is_signed { static const bool value = false; }; // par defaut // puis spécialisation pour les types signés : template<> struct is_signed<char> { static const bool value = true; }; template<> struct is_signed<signed char> { static const bool value = true; }; template<> struct is_signed<const signed char> { static const bool value = true; }; //etc.. pour short, int, long
En fait, si tu en as vraiment besoin, plutôt que de tout refaire (mal comme ci-dessus), tu as tout intérêt à piocher dans Boost.Types Trait. Regardes comme c'est implémenté pour apprendre un peu de méta-prog.
Pour les types primitifs, vu que leur nombre est fini et qu'ils sont connus à l'avance, on peut faire une classe de traits.
De même, peut-on savoir si un type est primitif ou "complexe" ? (i.e. "int" est primitif, "struct S {};" est complexe) : oui
Peut-on savoir si un type (struct ou class) possède des méthodes virtuel ? hum, j'ai un doute mais je crois pas. (ou alors c'est basé sur le compilo)
Peut-on savoir si un type est une référence ou un pointeur ? (avec leur variante const ou pas) oui
Peut-on savoir si un type est signé ou non ? oui
Peut-on savoir si un type est const ou non ? oui
Pour tous, voir boost::type_traits.
Sa se fait à la main aussi et c'est intéressant à faire. (boost::type_traits va très loin celà dis, ils utilisent les possibilités des compilos etc).
Par exemple pour savoir si c'est une ref :
C'est un peu hardcore pour savoir si le type est une fonction si je me souviens bien mais rien d'insurmontable.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 template <typename T> struct isRef { enum { Yes = 0 }; enum { No = !Yes }; typedef T baseType; }; template <> struct isRef<T&> { enum { Yes = 1 }; enum { No = !Yes }; typedef T baseType };
edit : grillé deux fois :').
Par contre, il me semble qu'on ne peut pas savoir si un type est POD.
oui : boost::is_polymorphic :mrgreen:
Ca se base sur le fait qu'une vtable ça prend de la place. Toujours (mal) écrit, ça voisine :
P.S. : comme dit dans mon code précédent, je n'ai aucun talent divinatoire. Je me contente pour apprendre de regarder l'implémentation de Boost.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 template<class T> struct is_polymorphique { private: struct dummy_1 : public T { ~dummy_1(); }; struct dummy_2 : public T { virtual ~dummy_2(); }; public : static const bool value = sizeof(dummy_1)==sizeof(dummy_2); };
Boost "triche".
Citation:
Envoyé par [url=http://www.boost.org/doc/libs/1_40_0/libs/type_traits/doc/html/boost_typetraits/reference/is_pod.html]Doc de boost[/url]
Ah oui en effet is_polymorphic j'avais oublié. Tricky :p.
Ah et à noté que le prochain standard prévoit lui aussi une classe de trait pour les types ;).
Nota : C'est utile par exemple pour avoir un "sélecteur" sur la façon de passez un argument à une fonction. (si le type est un type primitif on passe par valeur, si c'est un pointeur on laisse, si c'est autre chose une const_ref etc, boost propose ça aussi hein :p.)
ps : j'avais édité mon message mais vous avait poster entre temps du coup il est passé à la trappe (enfin rester sur la première page quoi).
edit : ouai voilà, is_pod est l'exemple typique où ils ont besoin de faire appel aux fonctions du compilos (et donc c'est pas _portable_)
Effectivement, pour visual par expl, il utilise le support du compilo pour les traits (__is_pod en l'occurrence). Et là, plus de méta prog, c'est du dur !
Tiens d'ailleurs, à propos de méta-programmation, cela faisait des années que je n'avais jamais rien bitté à la métaprog avant de soudainement recevoir une révélation cosmique grâce à cet article :
What Does Haskell Have to Do with C++?
Pourquoi personne n'avait jamais fait d'article aussi clair et simple avant ? :(
oufti 8O
:applo: :ccool:
Eh bien merci les gars.
Je connaissais un peu les boost::call_traits, voilà que je vais pouvoir affronter les type_traits désormais ;)
Mais, c'est quoi POD ? J'ai cru comprendre que c'est une structure "minimaliste", i.e. dont les membres sont eux-mêmes POD ou primitifs et dont les méthodes éventuelles sont non virtuelles, et pas de constructeur ni de destructeur explicites.
POD pour plain' old data, ça veut dire comme tu l'as dis que c'est soit un type primitifs soit une structure composé uniquement de type eux mêmes pod. les structs au sens C, les primitifs etc.
et :Citation:
Envoyé par The norme
[EDIT] grilled:aie:Citation:
Envoyé par The norme
Je rajoute juste qu'un type POD peut avoir des fonctions membres mais pas d'opérateur= ou de destructeur défini (ni de fonctions virtuelles bien sûr). La chose que j'oublie tout le temps est est-ce qu'une structure qui dérive d'une structure POD tout en respectant les critères est elle-même POD ? J'aurais tendance à dire non.
std::string n'est pas POD alors. Et toute structure contenant des std::string ne l'est pas non plus.
Comme tu dis les PODs sont donc des struct "C like".
Je me demande dans quel cas ça peut-être utile ce genre d'info.
L'inconvénient est la notion de "constructeur trivial": Ceci n'est pas considéré comme trivial:
Donc, on ne peut pas utiliser le raccourci return st_testPod2(42); si on veut que la structure soit POD (mais bon, on peut facilement faire une fonctionpour ça).Code:
1
2
3
4
5
6
7 struct st_testPod2 { int titi; explicit st_testPod2() : titi() {} explicit st_testPod2(int titi) : titi(titi) {} };
Merci.Citation:
Envoyé par Envoyé par The norme
Mais c'est le genre de prose qui dépasse mes capacités intellectuelles. J'ai beau lire 10 fois, ça reste incompréhensible pour moi :aie:
C'est là qu'on se dit qu'une image ou (un exemple) et son contraire valent 1000 mots ;)
Par exemple dans la STL de VS ou celle de GCC un appel à std::copy peut être optimisé en memove si le type pointé par l'itérateur est un pod.
Le code de la STL de gcc est relativement facile à suivre (comparé à celui de visual :aie:)
std::copy appelle tout d'abord __copy_move_a() qui va déterminer si le type est un pod ou non :
Si non, alors __simple == false, et on tombe au final sur une boucle for :Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 template<bool _IsMove, typename _II, typename _OI> inline _OI __copy_move_a(_II __first, _II __last, _OI __result) { typedef typename iterator_traits<_II>::value_type _ValueTypeI; typedef typename iterator_traits<_OI>::value_type _ValueTypeO; typedef typename iterator_traits<_II>::iterator_category _Category; const bool __simple = (__is_pod(_ValueTypeI) && __is_pointer<_II>::__value && __is_pointer<_OI>::__value && __are_same<_ValueTypeI, _ValueTypeO>::__value); return std::__copy_move<_IsMove, __simple, _Category>::__copy_m(__first, __last, __result); }
Par contre si __simple = true, c'est un memmove :Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 template<> struct __copy_move<false, false, random_access_iterator_tag> { template<typename _II, typename _OI> static _OI __copy_m(_II __first, _II __last, _OI __result) { typedef typename iterator_traits<_II>::difference_type _Distance; for(_Distance __n = __last - __first; __n > 0; --__n) { *__result = *__first; ++__first; ++__result; } return __result; } };
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13 template<bool _IsMove> struct __copy_move<_IsMove, true, random_access_iterator_tag> { template<typename _Tp> static _Tp* __copy_m(const _Tp* __first, const _Tp* __last, _Tp* __result) { __builtin_memmove(__result, __first, sizeof(_Tp) * (__last - __first)); return __result + (__last - __first); } ;
Joli ! :D
Au fait, pour savoir si un type est primitif, suffit-il d'utiliser __is_class() ? (et donc ==false)
EDIT: je me répond: pas tout à fait. Les références et pointeurs répondent aussi "false" à la question.
Arzar montrait l'exemple d'implémentation de GCC pour montrer l'utilité de ce genre d'info. Le code est donc le code de l'implémentation de la STL utilisée par GCC. A ce titre __is_class n'est pas standard et dépend du compilateur (ici, GCC).
Il faut attendre C++0x pour avoir des classes traits standards et en particulier un std::is_class.
On peut d'ailleurs attirer l'attention de tous sur le fait que, de manière générale, tout ce qui est préfixé par __ est propre à l'implémentation faite par les développeurs et n'est donc, par définition, ni standard ni garanti avec d'autres implémentations, que ce soit du point de vue du fournisseur ou du point de vue de la version...
OK, bien compris.
A partir du moment où autant GCC que MSVC supporte "ces petits machins" (__is_pod, __is_class), il n'y a pas trop de soucis à se faire. Il sera toujours temps de faire un "find and replace" dans l'ensemble du code pour les remplacer par leur équivalent std:: (il ne devrait pas y en avoir beaucoup...)
A conditions qu'ils aient le même sens pour les deux compilateurs...
Et ton code ne sera pas portable avec d'autres compilateurs.
Et pis, un jour il peuvent même disparaitre ou être renommé dans une nouvelle version de ces compilateurs. Bref beaucoup de contrainte et de maintenance que Boost.Trait t'épargne en attendant C++0x
Comme on essaye de le faire valoir:
Selon les besoins de ton projet, cela peut apporter pas mal de contraintes à la "portabilité" ;)
- Il n'y a pas que VC++ et Gcc (il y a les compilateurs intel, comeau et d'autres encore)
- ils peuvent exister dans la version 2.3 mais disparaitre dans la version 3 (voire, dans la version 2.4) et ne pas exister dans la version 1.9
- ...
J'ai cru comprendre que std::is_class et std::is_pod seront standards. Vous ai-je mal compris ?