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_)