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
Olivier Miakinen
Un exemple qui illustre ma question :

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

[...]
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.


Tu pourrais essayer avec :
#define CAT( a, b ) a b

;-)

Avatar
Fabien LE LEZ
On Fri, 30 Jun 2006 03:30:53 +0200, (Fabien
Chêne):

Mais je soupsonne Boost.Preprocessor
de le faire rien que pour m'embêter :-(


Dans ce cas, c'est soit une mauvaise utilisation de boost, soit un bug
qu'il faut leur remonter.

Avatar
fabien.chene
Fabien LE LEZ writes:

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 ne l'utilise pas directement. Mais je soupsonne Boost.Preprocessor
de le faire rien que pour m'embêter :-(

Je suppose que tu as une macro

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


C'est un peu plus complexe, pour prendre en compte les contraintes des
variants, mais ça revient à peu près à ça.

et tu veux rajouter une macro

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


En fait, c'est soi je réécris une macro spéciale pour cela, soit je
transforme la première via Boost.Preprocessor. La deuxième solution
est souple et bien plus élégante, mais elle ne doit pas devenir non
portable pour autant.


--
Fab


Avatar
fabien.chene
Fabien LE LEZ writes:

On Fri, 30 Jun 2006 03:30:53 +0200, (Fabien
Chêne):

Mais je soupsonne Boost.Preprocessor
de le faire rien que pour m'embêter :-(


Dans ce cas, c'est soit une mauvaise utilisation de boost, soit un bug
qu'il faut leur remonter.


Réflexion faite, BOOST_PP_LIST_FOR_EACH doit répondre à mon besoin. Si
je lui passe en paramètre une macro de concaténation toute bête :

#define CAT( a, b ) a b

Merci.

--
Fab


Avatar
fabien.chene
Olivier Miakinen <om+ writes:


Tu pourrais essayer avec :
#define CAT( a, b ) a b

;-)


Yep :-D

--
Fab

Avatar
kanze
Fabien Chêne wrote:
"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.


Mais c'est quoi, alors ? La définition du langage du C++ ne
parle pas de « mots », et je ne sais pas quelle signification
à le donner autre que celui de token. Le C++, c'est un langage
de programmation, avec une spécication plutôt précise. Ce n'est
pas comme le français, où on pourrait discuter si « a priori »
est un mot ou deux, ou même si « qu'on dirait-on » n'est pas,
en fait, un seul 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.


Je sais que :: est un token. Mais en quoi ::T, T:: n'en
serait-il pas un ?


Parce qu'ils sont des séquences de deux tokens.

T::type en est bien un ?

Non, c'est une séquence de trois tokens, qu'on peut aussi bien
écrire : « T :: /* un commentaire */ type ».

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.


N'est-ce pas ? Qu'on ait des cas de comportement indéfini à
l'exécution, parce que la vérification lors de la compilation
n'est pas computationnellement faisable, et que la vérification
lors de l'exécution coûterait horriblement cher en termes de
temps d'exécution, je veux bien. Mais que quelque chose pûrement
du compilateur donne un comportement indéfini...

Mais il y en a malheureusement beaucoup. On peut dire que même
si la norme dit « comportement indéfini », dans la pratique,
ou bien il y a une erreur à la compilation, ou bien ça marche.

Et c'est parce que ce comportement indéfini est si étonnant que
j'ai tenu à en donner un peu d'histoire. La motivation, c'est
que si un préprocesseur travaille surtout avec des chaînes de
caractères, il ne soit pas obligé à faire des vérifications en
plus.

M'enfin. Aurais-tu une référence sur la norme à ce sujet ?


§16.3.3/3 (définition de l'opérateur ##) :

For both object-like and function-like macro invocations,
before the replacement list is reexamined for more macro
names to replace, each instance of a ## preprocessing token
in the replacement list (not from an argument) is deleted
and the preceding preprocessing toke is concatentated with
the following preprocessing token. IF THE RESULT IS NOT A
VALID PREPROCESSING TOKEN, THE BEHAVIOR IS UNDEFINED. The
resulting token is available for further macro replacement.
The order of evaluation of ## operators is unspecified.

Pour une fois, la norme est parfaitement claire, et sans la
moindre ambiguïté.

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 ?


Aujourd'hui, zéro. §2.1/3 « [...] Each comment is replaced by
one space character. [...] ». C'est dans les phases de
traduction, et ça a lieu lors de la tokenisation. « a/**/b » est
l'équivalent de « a b ».

Dans le monde du C, il n'est pas rare qu'un compilateur ait des
options pour supporter des versions pré-standard, du C K&R1, par
exemple, mais aussi des préprocesseurs pré-standard. (Note
qu'avant la norme C ISO, il y avait deux normes de fait en ce
qui concernait les préprocesseurs. Deux norems de fait
incompatibles, évidemment. Et une des incompatibilités
touchaient précisement comment concaténer deux tokens.) Étant
donné que la plupart de compilateurs C++ partage un
préprocesseur avec un compilateur C, il se peut que tu trouves
des options pour supporter un préprocesseur pré-standard aussi
en C++.

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.


Tout à fait, et c'est un but très louable en soi. Le seul
problème, c'est quand en fait, le « comportement indéfini » a
en fait toujours marcher, partout, et de ce fait, se trouver
activement utiliser dans beaucoup de code. Même dans ces cas, en
avertir, c'est bien. Mais tout le monde n'a pas forcément envie
de réécrire tout leur code du jour à lendemain, même si
formellement, c'est incorrect.

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.


Alors là, c'est curieux. Parce que si -traditional-cpp doit
signifier ce que le nom semble, il faudrait bien l'accepter. De
l'autre côté : -traditional-cpp, est-ce le préprocesseur de
Riesser, ou celui de AT&T ? Ni l'un ni l'autre n'exigeait que
le résultat d'une concatenation soit un token. Mais ni l'un ni
l'autre ne connaissait ## non plus : Riesser utiliser a/**/b,
et AT&T a, puis un saut de ligne, et b au début de la ligne
suivant.

S'il interprète a/**/b comme une concatentation, et puis exige
que le résultat soit un seul token, c'est curieux.

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.


Mais je ne vois pas où il te faut la concatenation là dedans.

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 ...


Ni ici. Tu ne traites que des tokens et des séquences de tokens.
Tu n'as jamais besoin de convertir ce qui sont deux tokens en un
seul.

--
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
Olivier Miakinen

Mais c'est quoi, alors ? La définition du langage du C++ ne
parle pas de « mots », et je ne sais pas quelle signification
à le donner autre que celui de token. Le C++, c'est un langage
de programmation, avec une spécication plutôt précise. Ce n'est
pas comme le français, où on pourrait discuter si « a priori »
est un mot ou deux, ou même si « qu'on dirait-on » n'est pas,
en fait, un seul mot.


<HS>
L'exemple canonique est « pomme de terre » que nombre de linguistes
s'accordent à considérer comme un seul mot. Pour ce qui est du « qu'on
dirait-on », je suppose que tu voulais parler du « qu'en-dira-t-on »,
mais la présence de traits d'union rend la démonstration un peu moins
convainquante qu'avec « pomme de terre ».
</HS>

Je sais que :: est un token. Mais en quoi ::T, T:: n'en
serait-il pas un ?


Parce qu'ils sont des séquences de deux tokens.

T::type en est bien un ?

Non, c'est une séquence de trois tokens, qu'on peut aussi bien
écrire : « T :: /* un commentaire */ type ».


Je crois que c'était bien là l'origine de l'incompréhension de Fabien.

[...] (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 ?


Aujourd'hui, zéro. §2.1/3


Sauf qu'en l'occurrence, pour juxtaposer deux tokens sans en faire
un nouveau token, cela marchera sans problème. Exactement comme ma
proposition d'utiliser « a b » au lieu de « a ## b ».

« [...] Each comment is replaced by
one space character. [...] ». C'est dans les phases de
traduction, et ça a lieu lors de la tokenisation. « a/**/b » est
l'équivalent de « a b ».


Voilà. C'est équivalent, et c'est bien ce dont il a besoin. Mais c'est
vrai que du coup l'utilisation d'une macro est parfaitement inutile.

[...]


Mais je ne vois pas où il te faut la concatenation là dedans.

[...]

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


Ni ici. Tu ne traites que des tokens et des séquences de tokens.
Tu n'as jamais besoin de convertir ce qui sont deux tokens en un
seul.


Pile-poil.

--
Olivier Miakinen
Troll du plus sage chez les conviviaux : le nouveau venu, avec
son clan, s'infiltre dans les groupes de nouvelles. (3 c.)



Avatar
Jean-Marc Bourguet
Fabien LE LEZ writes:

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.


ca marche a coup sur dans son cas car il n'a pas besoin de faire un
token a partir de plusieurs.

Que ce soit en C++ ou en C, il me semble me souvenir avoir verifie que
si on respecte la lettre de la norme, ca ne devrait pas pouvoir
remplacer ##. Ca ne m'etonnerait pas que ca le puisse parfois en
pratique.

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
Fabien LE LEZ
On 30 Jun 2006 14:59:38 +0200, Jean-Marc Bourguet :

ca marche a coup sur dans son cas car il n'a pas besoin de faire un
token a partir de plusieurs.


Yep. Mais remplacer "/**/" par un simple espace marche tout aussi
bien...

Avatar
fabien.chene
"kanze" writes:

Fabien Chêne wrote:
"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.


Mais c'est quoi, alors ? La définition du langage du C++ ne
parle pas de « mots », et je ne sais pas quelle signification
à le donner autre que celui de token.


Pas d'autre, en effet. Je pensais à tord -- en fait, je n'y avais
jamais réfléchi -- que les espaces, commentaires, retours à la ligne,
étaient ce qui délimite les tokens ... Quelle naïveté !

À 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 ?


Parce qu'ils sont des séquences de deux tokens.

T::type en est bien un ?

Non, c'est une séquence de trois tokens, qu'on peut aussi bien
écrire : « T :: /* un commentaire */ type ».


Merci pour l'illumination salutaire !

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


Tout à fait, et c'est un but très louable en soi. Le seul
problème, c'est quand en fait, le « comportement indéfini » a
en fait toujours marcher, partout, et de ce fait, se trouver
activement utiliser dans beaucoup de code. Même dans ces cas, en
avertir, c'est bien. Mais tout le monde n'a pas forcément envie
de réécrire tout leur code du jour à lendemain, même si
formellement, c'est incorrect.


Entièrement d'accord. Le compilateur pourrait nous proposer une option
pour accepter le code. Je croyais que c'est ce qu'il était censé
fournir, pour ce qui concerne -traditional-cpp ...

Un autre cas qui a été évoqué ici, il y a quelques mois :

class A; std::vector<A> v; // A incomplet, comportement indéfini pour
// un composant de la bibliothèque standard

Une option dans GCC/g++ pour accepter ce code serait assez
appréciable. Le mieux serait que la prochaine norme l'accepte, est-ce
en prévision ?

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


Alors là, c'est curieux. Parce que si -traditional-cpp doit
signifier ce que le nom semble, il faudrait bien l'accepter. De
l'autre côté : -traditional-cpp, est-ce le préprocesseur de
Riesser, ou celui de AT&T ? Ni l'un ni l'autre n'exigeait que
le résultat d'une concatenation soit un token. Mais ni l'un ni
l'autre ne connaissait ## non plus : Riesser utiliser a/**/b,
et AT&T a, puis un saut de ligne, et b au début de la ligne
suivant.


La documentation de cette option est un peu spartiate, comment savoir
s'il imite un Riesser ou un AT&T ou autre ?

-traditional-cpp
Try to imitate the behavior of old-fashioned C preprocessors, as
opposed to ISO C preprocessors.

Vu le « Try », comment dire que l'option ne repecte pas le cahier des
charges ? :-)

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.


Mais je ne vois pas où il te faut la concatenation là dedans.


Effectivement, pas ici.

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 ...


Ni ici. Tu ne traites que des tokens et des séquences de tokens.
Tu n'as jamais besoin de convertir ce qui sont deux tokens en un
seul.


Oui, je croyais en avoir besoin. Mais ce n'est effectivement pas un
cas d'utilisation de ##.

--
Fab




1 2 3 4