OVH Cloud OVH Cloud

preprocesseur : utilisation de ## et de «::»

33 réponses
Avatar
fabien.chene
Bonsoir,

Est-il possible d'utiliser ## pour concaténer deux mots, dont le
deuxième est préfixé de «::» ?
Un exemple qui illustre ma question :

#define CAT( a, b ) a ## b

struct Foo
{
typedef int type;
};

template <class T>
CAT( typename T, ::type ) f( T )
{
CAT( typename T, ::type ) i = 2;
return i;
}

g++ rale et me dit que T:: type n'est pas un token de preprocessing
valide.

icc ne bronche pas. Est-ce légitime ? Même si ça ne l'est pas, y a
t'il une façon d'invoquer g++ pour obtenir un fonctionnement similaire
à icc ?

Merci.

--
Fab

10 réponses

1 2 3 4
Avatar
Frederic Lachasse
"Fabien chêne" wrote in message
news:

Bonsoir,

Est-il possible d'utiliser ## pour concaténer deux mots, dont le
deuxième est préfixé de «::» ?
Un exemple qui illustre ma question :

#define CAT( a, b ) a ## b

struct Foo
{
typedef int type;
};

template <class T>
CAT( typename T, ::type ) f( T )
{
CAT( typename T, ::type ) i = 2;
return i;
}

g++ rale et me dit que T:: type n'est pas un token de preprocessing
valide.

icc ne bronche pas. Est-ce légitime ? Même si ça ne l'est pas, y a
t'il une façon d'invoquer g++ pour obtenir un fonctionnement similaire
à icc ?


La règle pour ## dans une macro est un peu bizarre... Le truc est de faire
la concaténation par une autre macro:
#define CAT2(a,b) a ## b
#define CAT(a,b) CAT2(a,b)

Pour l'explication du pourquoi, c'est un peu trop limite pour moi (et je
cherche à limiter la consommation d'aspirine...). La seule chose importante
pour moi est que le truc marche...

--
Frédéric Lachasse - ECP86

Avatar
Fabien LE LEZ
On Thu, 29 Jun 2006 03:08:37 +0200, (Fabien
chêne):

template <class T>
CAT( typename T, ::type ) f( T )
{
CAT( typename T, ::type ) i = 2;


À quoi la macro sert-elle ici ?

Avatar
Jean-Marc Bourguet
(Fabien chêne) writes:

Bonsoir,

Est-il possible d'utiliser ## pour concaténer deux mots, dont le
deuxième est préfixé de «::» ?
Un exemple qui illustre ma question :

#define CAT( a, b ) a ## b

struct Foo
{
typedef int type;
};

template <class T>
CAT( typename T, ::type ) f( T )
{
CAT( typename T, ::type ) i = 2;
return i;
}

g++ rale et me dit que T:: type n'est pas un token de preprocessing
valide.


Il a raison.

icc ne bronche pas. Est-ce légitime ?


Non.

Pourquoi ne pas faire

typename T :: type i = 2;

Il n'y a aucune raison d'essayer de concatener ces 4 tokens (typename,
T, ::, type) pour en faire un seul.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
kanze
Fabien chêne wrote:

Est-il possible d'utiliser ## pour concaténer deux mots, dont
le deuxième est préfixé de «::» ?


Qu'est-ce que tu entends par « mot » ? À l'intérieur du
langage, on parle plutôt des tokens. Et il n'existe pas de token
« préfixé de `::' » ; :: est un token en soi.

La règle de base de l'opérateur ##, c'est qu'il prend deux
tokens, pour en faire un. Formellement, les paramètres de
l'opérateur sont le token qui le précède et le token qui le
suit. Et le résultat doit être aussi un token légal. Sinon,
c'est un comportement indéfini.

Un exemple qui illustre ma question :

#define CAT( a, b ) a ## b

struct Foo
{
typedef int type;
};

template <class T>
CAT( typename T, ::type ) f( T )
{
CAT( typename T, ::type ) i = 2;
return i;
}

g++ rale et me dit que T:: type n'est pas un token de
preprocessing valide.


Il a raison. Tu as un comportement indéfini.

icc ne bronche pas.


Il a raison. Ne pas broncher, c'est aussi un comportement permis
devant un programme qui a un comportement indéfini.

Est-ce légitime ?


La norme l'interdit. Historiquement, la plupart des compilateurs
l'acceptaient.

Historiquement (c-à-d les implémentations de C d'il y a vingt
ans), le préprocesseur travaillait avec des chaînes de
caractères et les sous-chaînes. Le comité C a inventé ## pour
remplacer de diverses astuces (qui variait d'un compilateur à
l'autre) qui servait à concaténer des tokens ; des astuces qui
dépendaient sur le fait que le préprocesseur travaillait sur les
chaînes de caractères, et qui dépendaient en fait des
« accidents » dans l'implémentation du préprocesseur. (Donc,
avec certains préprocesseurs, ton CAT s'écrivait a/**/b ; avec
d'autres, on mettait a à la fin de ligne, et b au début de la
ligne suivante. Deux comportements qui se basaient sur des cas
que K&R n'avaient pas spécifié.) Au même temps, le comité a
voulu spécifier le préprocesseur de façon qu'il puisse être
implémenter en traitant des tokens directement. D'où la règle
que le résultat soit un token. (Note bien qu'au niveau du
préprocesseur, le compilateur doit bien garder
l'« orthographie » des tokens. Dans ton cas, par exemple,
CAT( &, x ) serait illégal, CAT( and, x ) légal, bien que
dans les deux cas, le préprocesseur a affaire aux mêmes tokens.
Mais il n'a besoin de régarder cette orthographie que dans les
cas rarissime.)

Dans la pratique, fort peu de compilateurs ont profité de cette
possibilité, et la quasi-totalité des préprocesseurs travaillent
toujours avec les chaînes de caractères directement, et
maintiennent les tokens en tant que chaîne, ne les évaluant
qu'après toute expansion du macro. Du coup, ## revient à un
opérateur de concatenation de chaîne, et ton exemple ne pose pas
de problème. Dans le cas de g++, je ne sais pas si c'est
l'exception, et qu'il travaille internalement en token, ou si
simplement ils ont ajouté un test explicit pour donner une
erreur à cause du comportement indéfini.

Note aussi que ce que constitue un token dépend du contexte. Si
l'expansion des macros se base sur des tokens, et non des
chaînes, il faut propager ce contexte dans l'expansion, pour
savoir interpréter des chose comme <xxx.h> (qui serait un seul
token après un #include, ou peut-être trois, selon
l'interprétation de §16.2/3, mais cinq n'importe où ailleurs).

Même si ça ne l'est pas, y a t'il une façon d'invoquer g++
pour obtenir un fonctionnement similaire à icc ?


Étant donné que c'était aussi le comportement des anciens g++,
on s'y attendrait, n'est-ce pas ? Où que l'« erreur » ne soit
qu'un avertissement.

Toute fois est : je n'utiliserais pas de telles constructions
dans de nouveau code. L'intérêt d'une telle option dans g++, ça
serait uniquement de compiler du code ancien, pour qu'on ne soit
pas obligé à le modifier tout de suite. La construction est
illégale, et donc a évité. Et dans ton cas, je ne vois pas à
quoi il sert ; tu as une suite de tokens légaux qui font ce que
tu veux, tu n'as pas besoin de la concatenation.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
fabien.chene
"Frederic Lachasse" writes:

La règle pour ## dans une macro est un peu bizarre... Le truc est de faire
la concaténation par une autre macro:
#define CAT2(a,b) a ## b
#define CAT(a,b) CAT2(a,b)


Oui, j'ai déjà essayé cette ruse.

Pour l'explication du pourquoi, c'est un peu trop limite pour moi (et je
cherche à limiter la consommation d'aspirine...). La seule chose importante
pour moi est que le truc marche...


... mais le truc ne marche pas hélas :/

--
Fab

Avatar
Fabien LE LEZ
On Thu, 29 Jun 2006 23:25:22 +0200, (Fabien
Chêne):

(Donc,
avec certains préprocesseurs, ton CAT s'écrivait a/**/b ;


Ceci fonctionne


Il me semble qu'en C++, ça ne marche pas, car un commentaire est
considéré comme un séparateur.


Avatar
Fabien LE LEZ
On Thu, 29 Jun 2006 23:25:22 +0200, (Fabien
Chêne):

En gros, fabriquer une liste d'argument template à taille fixe, à
l'aide d'une macro, que je donne à manger à un boost::variant comme
ceci : boost::variant< MACRO_3( Foo, Bar, Baz ) > -- qui génère en
réalité boost::variant< Foo, Bar, Baz, none, none > si une certaine
macro vaut 5.

L'idée est maintenant de créer une macro pour, à partir de la liste
précédente, générer :

boost::variant< typename Foo::Type, typename Bar::Type, typename
Baz::Type, none, none >


Je n'ai toujours pas saisi l'utilité de ##.

Je suppose que tu as une macro

#define MACRO_3( x, y, z) x, y, z, none, none

et tu veux rajouter une macro

#define MACRO_machin( x, y, z)
MACRO_3 ( typename x::Type, typename y::Type, typename z::Type)



En pratique, ## ne sert que pour créer un identifiant (i.e. un "mot"
composé de lettres, chiffres et "_") à partir de deux identifiants.
Par exemple, pour créer "DEBUG_truc" à partir de "DEBUG_" et de
"truc".

Avatar
fabien.chene
"kanze" writes:

Fabien chêne wrote:

Est-il possible d'utiliser ## pour concaténer deux mots, dont
le deuxième est préfixé de «::» ?


Qu'est-ce que tu entends par « mot » ?


Justement ! J'ai employé « mot » pour ne pas dire token. Car je
n'étais pas sur que ça en soit un.

À l'intérieur du langage, on parle plutôt des tokens. Et il n'existe
pas de token « préfixé de `::' » ; :: est un token en soi.


Je sais que :: est un token. Mais en quoi ::T, T:: n'en serait-il pas
un ? T::type en est bien un ?
Je sens que je vais recourrir à une feinte du père Lafeinte genre :
CAT( typename F, oo::type )
Mais j'aurais préféré ne pas en arriver là.


g++ rale et me dit que T:: type n'est pas un token de
preprocessing valide.


Il a raison. Tu as un comportement indéfini.

icc ne bronche pas.


Il a raison. Ne pas broncher, c'est aussi un comportement permis
devant un programme qui a un comportement indéfini.

Est-ce légitime ?


La norme l'interdit.


J'aurais préféré que la norme l'autorise, ou déclare le programme
ill-formed. M'enfin. Aurais-tu une référence sur la norme à ce sujet ?

Historiquement, la plupart des compilateurs
l'acceptaient.

Historiquement (c-à-d les implémentations de C d'il y a vingt
ans), le préprocesseur travaillait avec des chaînes de
caractères et les sous-chaînes. Le comité C a inventé ## pour
remplacer de diverses astuces (qui variait d'un compilateur à
l'autre) qui servait à concaténer des tokens ; des astuces qui
dépendaient sur le fait que le préprocesseur travaillait sur les
chaînes de caractères, et qui dépendaient en fait des
« accidents » dans l'implémentation du préprocesseur. (Donc,
avec certains préprocesseurs, ton CAT s'écrivait a/**/b ;


Ceci fonctionne o/ J'avais complètement oublié ce hack. Quel en est
la portabilité, concrètement ?

avec d'autres, on mettait a à la fin de ligne, et b au début de la
ligne suivante. Deux comportements qui se basaient sur des cas que
K&R n'avaient pas spécifié.) Au même temps, le comité a voulu
spécifier le préprocesseur de façon qu'il puisse être implémenter en
traitant des tokens directement. D'où la règle que le résultat soit
un token. (Note bien qu'au niveau du préprocesseur, le compilateur
doit bien garder l'« orthographie » des tokens. Dans ton cas, par
exemple, CAT( &, x ) serait illégal, CAT( and, x ) légal, bien que
dans les deux cas, le préprocesseur a affaire aux mêmes tokens.
Mais il n'a besoin de régarder cette orthographie que dans les cas
rarissime.)

Dans la pratique, fort peu de compilateurs ont profité de cette
possibilité, et la quasi-totalité des préprocesseurs travaillent
toujours avec les chaînes de caractères directement, et
maintiennent les tokens en tant que chaîne, ne les évaluant
qu'après toute expansion du macro. Du coup, ## revient à un
opérateur de concatenation de chaîne, et ton exemple ne pose pas
de problème. Dans le cas de g++, je ne sais pas si c'est
l'exception, et qu'il travaille internalement en token, ou si
simplement ils ont ajouté un test explicit pour donner une
erreur à cause du comportement indéfini.


Je crois que cela correspond à la politique de GCC que j'observe,
diagnostiquer les comportements indéfinis comme des erreurs à la
compilation.

Car on sent bien que cpp est sur le point de craquer, le résultat du
préprocessing est comme je m'y attends, mais il sort en erreur.


Même si ça ne l'est pas, y a t'il une façon d'invoquer g++
pour obtenir un fonctionnement similaire à icc ?


Étant donné que c'était aussi le comportement des anciens g++,
on s'y attendrait, n'est-ce pas ?


Voui.

Où que l'« erreur » ne soit qu'un avertissement.


La première idée que j'ai eu fut d'invoquer g++ -E -P
-traditional-cpp, mais curieusement, sans plus de succès.


Toute fois est : je n'utiliserais pas de telles constructions
dans de nouveau code. L'intérêt d'une telle option dans g++, ça
serait uniquement de compiler du code ancien, pour qu'on ne soit
pas obligé à le modifier tout de suite. La construction est
illégale, et donc a évité.


Je pense que c'est raisonnable en effet.

Et dans ton cas, je ne vois pas à quoi il sert ; tu as une suite de
tokens légaux qui font ce que tu veux, tu n'as pas besoin de la
concatenation.


Ce n'était qu'un exemple servant à cerner le problème. La réalité est
bien plus effrayante :-)

En gros, fabriquer une liste d'argument template à taille fixe, à
l'aide d'une macro, que je donne à manger à un boost::variant comme
ceci : boost::variant< MACRO_3( Foo, Bar, Baz ) > -- qui génère en
réalité boost::variant< Foo, Bar, Baz, none, none > si une certaine
macro vaut 5.

L'idée est maintenant de créer une macro pour, à partir de la liste
précédente, générer :

boost::variant< typename Foo::Type, typename Bar::Type, typename
Baz::Type, none, none >

C'est la que le problème des :: se pose ...


--
Fab




















Fabien chêne wrote:

Est-il possible d'utiliser ## pour concaténer deux mots, dont
le deuxième est préfixé de «::» ?


Qu'est-ce que tu entends par « mot » ? À l'intérieur du
langage, on parle plutôt des tokens. Et il n'existe pas de token
« préfixé de `::' » ; :: est un token en soi.

La règle de base de l'opérateur ##, c'est qu'il prend deux
tokens, pour en faire un. Formellement, les paramètres de
l'opérateur sont le token qui le précède et le token qui le
suit. Et le résultat doit être aussi un token légal. Sinon,
c'est un comportement indéfini.

Un exemple qui illustre ma question :

#define CAT( a, b ) a ## b

struct Foo
{
typedef int type;
};

template <class T>
CAT( typename T, ::type ) f( T )
{
CAT( typename T, ::type ) i = 2;
return i;
}

g++ rale et me dit que T:: type n'est pas un token de
preprocessing valide.


Il a raison. Tu as un comportement indéfini.

icc ne bronche pas.


Il a raison. Ne pas broncher, c'est aussi un comportement permis
devant un programme qui a un comportement indéfini.

Est-ce légitime ?


La norme l'interdit. Historiquement, la plupart des compilateurs
l'acceptaient.

Historiquement (c-à-d les implémentations de C d'il y a vingt
ans), le préprocesseur travaillait avec des chaînes de
caractères et les sous-chaînes. Le comité C a inventé ## pour
remplacer de diverses astuces (qui variait d'un compilateur à
l'autre) qui servait à concaténer des tokens ; des astuces qui
dépendaient sur le fait que le préprocesseur travaillait sur les
chaînes de caractères, et qui dépendaient en fait des
« accidents » dans l'implémentation du préprocesseur. (Donc,
avec certains préprocesseurs, ton CAT s'écrivait a/**/b ; avec
d'autres, on mettait a à la fin de ligne, et b au début de la
ligne suivante. Deux comportements qui se basaient sur des cas
que K&R n'avaient pas spécifié.) Au même temps, le comité a
voulu spécifier le préprocesseur de façon qu'il puisse être
implémenter en traitant des tokens directement. D'où la règle
que le résultat soit un token. (Note bien qu'au niveau du
préprocesseur, le compilateur doit bien garder
l'« orthographie » des tokens. Dans ton cas, par exemple,
CAT( &, x ) serait illégal, CAT( and, x ) légal, bien que
dans les deux cas, le préprocesseur a affaire aux mêmes tokens.
Mais il n'a besoin de régarder cette orthographie que dans les
cas rarissime.)

Dans la pratique, fort peu de compilateurs ont profité de cette
possibilité, et la quasi-totalité des préprocesseurs travaillent
toujours avec les chaînes de caractères directement, et
maintiennent les tokens en tant que chaîne, ne les évaluant
qu'après toute expansion du macro. Du coup, ## revient à un
opérateur de concatenation de chaîne, et ton exemple ne pose pas
de problème. Dans le cas de g++, je ne sais pas si c'est
l'exception, et qu'il travaille internalement en token, ou si
simplement ils ont ajouté un test explicit pour donner une
erreur à cause du comportement indéfini.

Note aussi que ce que constitue un token dépend du contexte. Si
l'expansion des macros se base sur des tokens, et non des
chaînes, il faut propager ce contexte dans l'expansion, pour
savoir interpréter des chose comme <xxx.h> (qui serait un seul
token après un #include, ou peut-être trois, selon
l'interprétation de §16.2/3, mais cinq n'importe où ailleurs).

Même si ça ne l'est pas, y a t'il une façon d'invoquer g++
pour obtenir un fonctionnement similaire à icc ?


Étant donné que c'était aussi le comportement des anciens g++,
on s'y attendrait, n'est-ce pas ? Où que l'« erreur » ne soit
qu'un avertissement.

Toute fois est : je n'utiliserais pas de telles constructions
dans de nouveau code. L'intérêt d'une telle option dans g++, ça
serait uniquement de compiler du code ancien, pour qu'on ne soit
pas obligé à le modifier tout de suite. La construction est
illégale, et donc a évité. Et dans ton cas, je ne vois pas à
quoi il sert ; tu as une suite de tokens légaux qui font ce que
tu veux, tu n'as pas besoin de la concatenation.


--
Fab


Avatar
fabien.chene
Fabien LE LEZ writes:

On Thu, 29 Jun 2006 03:08:37 +0200, (Fabien
chêne):

template <class T>
CAT( typename T, ::type ) f( T )
{
CAT( typename T, ::type ) i = 2;


À quoi la macro sert-elle ici ?


A illustrer le problème, rien de plus. Voir ma réponse à James
pour le rapport avec le code réel.

--
Fab


Avatar
fabien.chene
Jean-Marc Bourguet writes:

(Fabien chêne) writes:

Bonsoir,

Est-il possible d'utiliser ## pour concaténer deux mots, dont le
deuxième est préfixé de «::» ?
Un exemple qui illustre ma question :

#define CAT( a, b ) a ## b

struct Foo
{
typedef int type;
};

template <class T>
CAT( typename T, ::type ) f( T )
{
CAT( typename T, ::type ) i = 2;
return i;
}

g++ rale et me dit que T:: type n'est pas un token de preprocessing
valide.


Il a raison.

icc ne bronche pas. Est-ce légitime ?


Non.


Quel dommage !

Pourquoi ne pas faire

typename T :: type i = 2;

Il n'y a aucune raison d'essayer de concatener ces 4 tokens (typename,
T, ::, type) pour en faire un seul.


En réalité, ce sont les 2 tokens centraux qui sont concaténés (T, ::),
mais apparemment, cela ne produit pas un token valide (?)

Pour la raison, voir ma réponse à James -- bien que peu détaillée.

--
Fab


1 2 3 4