OVH Cloud OVH Cloud

include avec define ?

34 réponses
Avatar
Amerio
Bonjour,

J'ai un code dont l'interface est commune a toutes mes plateformes, mais
dont l'implémentation est fortement spécifique.
Ex : controller/Joypad.h et controller/src/PS2/Joypad.h et
controller/src/XBOX/Joypad.h
(oui ce sont des .h, inline oblige)

Dans le code source (controller/src/Joypad.cpp), on doit inclure le bon
src/???/Joypad.h
Actuellement, on fait cela, sans pb (target défini a PS2 ou XBOX, etc..):
#define PATH_CONTROLLER_JOYPAD <controller/src/##TARGET##/Joypad.h>
#include PATH_CONTROLLER_JOYPAD

Mais cette technique ne marche plus avec le dernier (?) gcc (au passage :
impossible de changer de version de gcc, cette version est imposée : 3.3.3
custom). Il nous jete avec le msg :
pasting "/" and "TARGET" does not give a valid preprocessing token

Ma question : cette facon de faire (define puis include) me semblait la plus
simple. Mais est elle standard ? Y-a-t-il un autre moyen ? (sans faire 36
#if (TARGET==XXX) etc)

10 réponses

1 2 3 4
Avatar
James Kanze
Gabriel Dos Reis writes:

|> James Kanze writes:

|> | Gabriel Dos Reis writes:

|> | |> "Amerio" writes:

|> | |> | J'ai un code dont l'interface est commune a toutes mes
|> | |> | plateformes, mais dont l'implémentation est fortement
|> | |> | spécifique. Ex : controller/Joypad.h et
|> | |> | controller/src/PS2/Joypad.h et controller/src/XBOX/Joypad.h
|> | |> | (oui ce sont des .h, inline oblige)

|> | |> | Dans le code source (controller/src/Joypad.cpp), on doit
|> | |> | inclure le bon src/???/Joypad.h Actuellement, on fait cela,
|> | |> | sans pb (target défini a PS2 ou XBOX, etc..):
|> | |> | #define PATH_CONTROLLER_JOYPAD <controller/src/##TARGET##/Joypad.h>
|> | |> | #include PATH_CONTROLLER_JOYPAD

|> | |> | Mais cette technique ne marche plus avec le dernier (?) gcc
|> | |> | (au passage : impossible de changer de version de gcc, cette
|> | |> | version est imposée : 3.3.3 custom). Il nous jete avec le
|> | |> | msg : pasting "/" and "TARGET" does not give a valid
|> | |> | preprocessing token

|> | |> C'est que tu dois « stringifier » "/" avant de le concatener
|> | |> avec TARGET. En fait, tu dois « stringifier » toute la ligne,
|> | |> où ne concatener que des opérandes « stringifiées ».

|> | Formellement, dans ce contexte-ci, la stringification n'est pas légale,

|> pour quelles raisons ?

Parce que la stringification donne toujours une constante de chaîne de
caractêres (« string literal »), et une constante de chaîne de
caractères n'est pas légale dans ce contexte.

|> | au moins selon la norme.

|> Chapitres et versets ?

§16.2 et §16.3.2. C'est on ne peut plus clair, pour ceux qui savent
lire.

|> | Évidemment, je ne connais pas de compilateur où il ne fonctionne
|> | pas.

|> Est-ce qu'il t'es venu à l'esprit que cela marche aussi parce que
|> c'est ce que demande la norme ?

Vue que ce n'est pas ce que démande la norme. Au moins pas dans son
texte normatif. C'est en revanche une technique assez répandu ; une
implémentation résponsable ferait en sorte qu'elle marche. De même
qu'elle ferait en sorte que la technique utilisée originalement
marcherait. (La norme n'est pas tout, et les implémentations responables
ne cassent pas le code sans raison.)

--
James Kanze
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
James Kanze
Gabriel Dos Reis writes:

|> James Kanze writes:

|> | Gabriel Dos Reis writes:

|> | |> James Kanze writes:

|> | |> | Gabriel Dos Reis writes:

|> | |> | |> "Amerio" writes:

|> | |> | |> | J'ai un code dont l'interface est commune a toutes mes
|> | |> | |> | plateformes, mais dont l'implémentation est fortement
|> | |> | |> | spécifique. Ex : controller/Joypad.h et
|> | |> | |> | controller/src/PS2/Joypad.h et
|> | |> | |> | controller/src/XBOX/Joypad.h (oui ce sont des .h,
|> | |> | |> | inline oblige)

|> | |> | |> | Dans le code source (controller/src/Joypad.cpp), on
|> | |> | |> | doit inclure le bon src/???/Joypad.h Actuellement, on
|> | |> | |> | fait cela, sans pb (target défini a PS2 ou XBOX,
|> | |> | |> | etc..):

|> | |> | |> | #define PATH_CONTROLLER_JOYPAD <controller/src/##TARGET##/Joypad.h>
|> | |> | |> | #include PATH_CONTROLLER_JOYPAD

|> | |> | |> | Mais cette technique ne marche plus avec le dernier
|> | |> | |> | (?) gcc (au passage : impossible de changer de version
|> | |> | |> | de gcc, cette version est imposée : 3.3.3 custom). Il
|> | |> | |> | nous jete avec le msg : pasting "/" and "TARGET" does
|> | |> | |> | not give a valid preprocessing token

|> | |> | |> C'est que tu dois « stringifier » "/" avant de le
|> | |> | |> concatener avec TARGET. En fait, tu dois « stringifier »
|> | |> | |> toute la ligne, où ne concatener que des opérandes «
|> | |> | |> stringifiées ».

|> | |> | Formellement, dans ce contexte-ci, la stringification n'est
|> | |> | pas légale,

|> | |> pour quelles raisons ?

|> | Parce que la stringification donne toujours une constante de
|> | chaîne de caractêres (« string literal »),

|> oui et tu sais ce que c'est ?

Un « string literal » ? Je sais ce que dit la norme, oui. Et dans la
contexte de la norme, c'est ce que dit la norme.

Parmi d'autres, c'est un token du préprocesseur d'un type bien
déterminé.

|> | et une constante de chaîne de caractères n'est pas légale dans ce
|> | contexte.

|> Groumph, lis un peu le texte pour savoir ce qu'est une chaîne de
|> caractères et ce que fait l'opérateur # !

|> | |> | au moins selon la norme.

|> | |> Chapitres et versets ?

|> | §16.2 et §16.3.2. C'est on ne peut plus clair, pour ceux qui
|> | savent lire.

|> Donc, je suppose que tu sais lire, puisque cela te paraît clair.
|> Cependant, il me paraît évident que la fonction qui permet de
|> comprendre ce que tu lis n'a pas été activée pendant ta studieuse
|> lecture. Je te suggère donc de l'activer et de lire :

[Description de ce que c'est qu'un « string literal » supprimé...]

Ce qui est exactement ce que j'ai dit, n'est pas. Le résultat de la
stringification, c'est bien un « string literal ».

Maintenant, lis §16.2 et montre-moi où elle dit qu'on a droit à un
« string literal » dans un include. Tout ce que je vois là, ce sont des
q-char-sequence et des h-char-sequence. Et pour utiliser un
q-char-sequence, il faut le mettre entre des "...".

En fait, je crois bien que c'était l'intention des auteurs que ça
marche. De même qu'il en était l'intention que la concatenation des
tokens du genre blah ## / ## filename.h puisse servir entre < et >. Mais
ce n'est pas ce qu'ils ont écrit. (Mais je crois que l'intention, c'est
que ça marchait comme si on faisait des substitutions textuelles, au
nombres d'espaces dans les séparateurs près, éventuellement.)

Dans la pratique, évidemment, à moins de se moquer de leurs
utilisateurs, un compilateur ferait en sorte que les deux fonctionnent.

|> | |> | Évidemment, je ne connais pas de compilateur où il ne
|> | |> | fonctionne pas.

|> | |> Est-ce qu'il t'es venu à l'esprit que cela marche aussi parce
|> | |> que c'est ce que demande la norme ?

|> | Vue que ce n'est pas ce que démande la norme.

|> Uniquement dans ton imagination. Si tu savais vraiment lire comme tu
|> le prétends, tu aurais vu l'exemple que donne la norme pour
|> illustrer pour des gens qui n'auraient pas compris.

Je sais qu'il y a des cas dans la norme où les exemples contradisent le
texte normatif. C'est une raison de plus de ne pas casser le code qui
existe.

--
James Kanze
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
Gabriel Dos Reis
James Kanze writes:

| Gabriel Dos Reis writes:
|
| |> James Kanze writes:
|
| |> | Gabriel Dos Reis writes:
|
| |> | |> James Kanze writes:
|
| |> | |> | Gabriel Dos Reis writes:
|
| |> | |> | |> "Amerio" writes:
|
| |> | |> | |> | J'ai un code dont l'interface est commune a toutes mes
| |> | |> | |> | plateformes, mais dont l'implémentation est fortement
| |> | |> | |> | spécifique. Ex : controller/Joypad.h et
| |> | |> | |> | controller/src/PS2/Joypad.h et
| |> | |> | |> | controller/src/XBOX/Joypad.h (oui ce sont des .h,
| |> | |> | |> | inline oblige)
|
| |> | |> | |> | Dans le code source (controller/src/Joypad.cpp), on
| |> | |> | |> | doit inclure le bon src/???/Joypad.h Actuellement, on
| |> | |> | |> | fait cela, sans pb (target défini a PS2 ou XBOX,
| |> | |> | |> | etc..):
|
| |> | |> | |> | #define PATH_CONTROLLER_JOYPAD <controller/src/##TARGET##/Joypad.h>
| |> | |> | |> | #include PATH_CONTROLLER_JOYPAD
|
| |> | |> | |> | Mais cette technique ne marche plus avec le dernier
| |> | |> | |> | (?) gcc (au passage : impossible de changer de version
| |> | |> | |> | de gcc, cette version est imposée : 3.3.3 custom). Il
| |> | |> | |> | nous jete avec le msg : pasting "/" and "TARGET" does
| |> | |> | |> | not give a valid preprocessing token
|
| |> | |> | |> C'est que tu dois « stringifier » "/" avant de le
| |> | |> | |> concatener avec TARGET. En fait, tu dois « stringifier »
| |> | |> | |> toute la ligne, où ne concatener que des opérandes «
| |> | |> | |> stringifiées ».
|
| |> | |> | Formellement, dans ce contexte-ci, la stringification n'est
| |> | |> | pas légale,
|
| |> | |> pour quelles raisons ?
|
| |> | Parce que la stringification donne toujours une constante de
| |> | chaîne de caractêres (« string literal »),
|
| |> oui et tu sais ce que c'est ?
|
| Un « string literal » ? Je sais ce que dit la norme, oui. Et dans la
| contexte de la norme, c'est ce que dit la norme.

Bien, on fait des progrès. Est-ce que tu sais aussi ce qu'est un
remplacement de macro ?

| Parmi d'autres, c'est un token du préprocesseur d'un type bien
| déterminé.

Woaw.

| |> | et une constante de chaîne de caractères n'est pas légale dans ce
| |> | contexte.
|
| |> Groumph, lis un peu le texte pour savoir ce qu'est une chaîne de
| |> caractères et ce que fait l'opérateur # !
|
| |> | |> | au moins selon la norme.
|
| |> | |> Chapitres et versets ?
|
| |> | §16.2 et §16.3.2. C'est on ne peut plus clair, pour ceux qui
| |> | savent lire.
|
| |> Donc, je suppose que tu sais lire, puisque cela te paraît clair.
| |> Cependant, il me paraît évident que la fonction qui permet de
| |> comprendre ce que tu lis n'a pas été activée pendant ta studieuse
| |> lecture. Je te suggère donc de l'activer et de lire :
|
| [Description de ce que c'est qu'un « string literal » supprimé...]
|
| Ce qui est exactement ce que j'ai dit, n'est pas. Le résultat de la
| stringification, c'est bien un « string literal ».

Tu as répété cette partie là, mais cela ne suffit pas pour conclure
que le code posté par Jean-Marc est invalide. Tout au contraire --
parce que dans son code, il y a expansion, dans l'expansion, la chaîne
de caractère est substituée pour produire exactement ce qu'il contente
#include.

| Maintenant, lis §16.2 et montre-moi où elle dit qu'on a droit à un
| « string literal » dans un include. Tout ce que je vois là, ce sont des

Voir ci-dessus. Considère le code tel que posté par Jean-Marc

#define TARGET PS2
#define XSTR(x) #x
#define STR(x) XSTR(x)
#define PLATFILE(base,file) STR(base/TARGET/file)

#include PLATFILE(controller/src,Joypad.h)


Examinons pas à pas ce qui arrive à la dernière ligne:

(1) Elle est décomposée en suite de pp-tokens comme suit:

{#}{include} {PLATFILE}{(}{controller}{/}{src}{,}{Joypad.h}{)}

(2) À la phase de traduction 4:

Preprocessing directives are executed and macro invocations are
expanded. If a character sequence that matches the syntax of a
universal-character-name is produced by token concatenation
(16.3.3), the behavior is undefined. A #include preprocessing
directive causes the named header or source file to be processed
from phase 1 through phase 4, recursively.

Pour exécuter la directive ci-dessus, 16.2/4 ordonne:

A preprocessing directive of the form

# include pp-tokens new-line

(that does not match one of the two previous forms) is
permitted. The preprocessing tokens after include in the
directive are processed just as in normal text (each identifier
currently defined as a macro name is replaced by its replacement
list of preprocessing tokens). If the directive resulting after
all replacements does not match one of the two previous forms,
the behavior is undefined.140) The method by which a sequence of
preprocessing tokens between a < and a > preprocessing token
pair or a pair of " characters is combined into a single header
name preprocessing token is implementation-defined.

(3) Nous sommes amenés à faire expansion de la macro fonctionnelle
PLATFILE. La sémantique est dictée par 16.3.1/1 :

After the arguments for the invocation of a function-like macro
have been identified, argument substitution takes place. A
parameter in the replacement list, unless preceded by a # or ##
preprocessing token or followed by a ## preprocessing token (see
below), is replaced by the corresponding argument after all macros
contained therein have been expanded. Before being substituted,
each argument s preprocessing tokens are completely macro replaced
as if they formed the rest of the translation unit; no other
preprocessing tokens are available.

Les arguments dans l'invocation de la macro fonctionnelle PLATFILE
sont:

(a) {controller}{/}{src} pour le paramètre base ;
(b) {Joypad.h} pour le paramètre file.

La liste de remplacement est {STR}{(}{base}{/}{TARGET}{/}{file}{)}.
Après susbtitution, nous avons :

{STR}{(}{controller}{/}{src}{/}{TARGET}{/}{Joypad.h}{)}


(4) Le traitement n'est pas fini, nous enseigne 16.3.4/1

After all parameters in the replacement list have been
substituted, the resulting preprocessing token sequence is
rescanned with all subsequent preprocessing tokens of the
source file for more macro names to replace.

Donc nous devons faire une expansion pour STR. Ce nous donne

{XSTR}{(}{controller}{/}{src}{/}{PS2}{/}{Joypad.h}{)}

car comme le demande 16.3.1/1, nous devons d'abord faire une
substitution de TARGET qui est utilisée dans l'argument pour STR

(5) La macro XSTR utilise l'opérateur # de stringification. Donc, nous
devons suivre les prescriptions de 16.3.2/2

If, in the replacement list, a parameter is immediately preceded
by a # preprocessing token, both are replaced by a single
character string literal preprocessing token that contains the
spelling of the preprocessing token sequence for the
corresponding argument. Each occurrence of white space between
the argument s preprocessing tokens becomes a single space
character in the character string literal. White space before
the first preprocessing token and after the last preprocessing
token comprising the argument is deleted. Otherwise, the
original spelling of each preprocessing token in the argument is
retained in the character string literal, except for special
handling for producing the spelling of string literals and
character literals: a character is inserted before each " and
character of a character literal or string literal (including
the delimiting " characters). If the replacement that results is
not a valid character string literal, the behavior is
undefined. The order of evaluation of # and ## operators is
unspecified.

Suivant ces préceptes, l'expansion de la macro XSTR pour l'argument
donné ci-dessus produit

"controller/src/PS2/Joypad.h"

(6) Revenant à la directive #include PLATFILE(controller/src,Joypad.h)
à la base de ce long détour, après expansion de macro comme le
veut 16.2/4, elle donne

#include "controller/src/PS2/Joypad.h"

qui est bien de la forme scolaire dont tu parles juste ci-dessous

| q-char-sequence et des h-char-sequence. Et pour utiliser un
| q-char-sequence, il faut le mettre entre des "...".

Ben oui, et c'est bien ce que fait l'opérateur de stringification.
CQFD.

| En fait, je crois bien que c'était l'intention des auteurs que ça

Ce n'est pas seulement l'intention, mais c'est aussi ce qui est écrit.

[...]

| |> | Vue que ce n'est pas ce que démande la norme.
|
| |> Uniquement dans ton imagination. Si tu savais vraiment lire comme tu
| |> le prétends, tu aurais vu l'exemple que donne la norme pour
| |> illustrer pour des gens qui n'auraient pas compris.
|
| Je sais qu'il y a des cas dans la norme où les exemples contradisent le
| texte normatif.

Mais je te parle du cas *concret* sous la main -- il n'est pas
question ici de généralités vaseuses.

| C'est une raison de plus de ne pas casser le code qui existe.

Bof. Si le code est invalide, il sera cassé tôt ou tard, ce n'est
qu'une question de temps. Et ceux qui disent le contraire en
prétendant des discussions fantomiques qui auraient eu lieu il y a une
dizaine d'annés déservent ce qu'ils méritent.

-- Gaby
Avatar
kanze
Gabriel Dos Reis wrote in message
news:...
James Kanze writes:

| Gabriel Dos Reis writes:

| |> James Kanze writes:

| |> | Gabriel Dos Reis writes:

| |> | |> James Kanze writes:

| |> | |> | Gabriel Dos Reis writes:

| |> | |> | |> "Amerio" writes:

| |> | |> | |> | J'ai un code dont l'interface est commune a toutes
| |> | |> | |> | mes plateformes, mais dont l'implémentation est
| |> | |> | |> | fortement spécifique. Ex : controller/Joypad.h et
| |> | |> | |> | controller/src/PS2/Joypad.h et
| |> | |> | |> | controller/src/XBOX/Joypad.h (oui ce sont des .h,
| |> | |> | |> | inline oblige)

| |> | |> | |> | Dans le code source (controller/src/Joypad.cpp),
| |> | |> | |> | on doit inclure le bon src/???/Joypad.h
| |> | |> | |> | Actuellement, on fait cela, sans pb (target défini
| |> | |> | |> | a PS2 ou XBOX, etc..):

| |> | |> | |> | #define PATH_CONTROLLER_JOYPAD <controller/src/##TARGET##/Joypad.h>
| |> | |> | |> | #include PATH_CONTROLLER_JOYPAD

| |> | |> | |> | Mais cette technique ne marche plus avec le
| |> | |> | |> | dernier (?) gcc (au passage : impossible de
| |> | |> | |> | changer de version de gcc, cette version est
| |> | |> | |> | imposée : 3.3.3 custom). Il nous jete avec le msg
| |> | |> | |> | : pasting "/" and "TARGET" does not give a valid
| |> | |> | |> | preprocessing token

| |> | |> | |> C'est que tu dois « stringifier » "/" avant de le
| |> | |> | |> concatener avec TARGET. En fait, tu dois «
| |> | |> | |> stringifier » toute la ligne, où ne concatener que
| |> | |> | |> des opérandes « stringifiées ».

| |> | |> | Formellement, dans ce contexte-ci, la stringification
| |> | |> | n'est pas légale,

| |> | |> pour quelles raisons ?

| |> | Parce que la stringification donne toujours une constante de
| |> | chaîne de caractêres (« string literal »),

| |> oui et tu sais ce que c'est ?

| Un « string literal » ? Je sais ce que dit la norme, oui. Et dans la
| contexte de la norme, c'est ce que dit la norme.

Bien, on fait des progrès. Est-ce que tu sais aussi ce qu'est un
remplacement de macro ?


Là, j'avoue être moins sûr. Parce qu'il y a la tradition (où c'est une
substitution de texte), et il y a la norme qui rompe (exprès, je crois)
avec la tradition, et qui le traite comme une substitution de tokens (et
non de texte).

| Parmi d'autres, c'est un token du préprocesseur d'un type bien
| déterminé.

Woaw.

| |> | et une constante de chaîne de caractères n'est pas légale dans
| |> | ce contexte.

| |> Groumph, lis un peu le texte pour savoir ce qu'est une chaîne de
| |> caractères et ce que fait l'opérateur # !

| |> | |> | au moins selon la norme.

| |> | |> Chapitres et versets ?

| |> | §16.2 et §16.3.2. C'est on ne peut plus clair, pour ceux qui
| |> | savent lire.

| |> Donc, je suppose que tu sais lire, puisque cela te paraît clair.
| |> Cependant, il me paraît évident que la fonction qui permet de
| |> comprendre ce que tu lis n'a pas été activée pendant ta
| |> studieuse lecture. Je te suggère donc de l'activer et de lire :

| [Description de ce que c'est qu'un « string literal » supprimé...]

| Ce qui est exactement ce que j'ai dit, n'est pas. Le résultat de la
| stringification, c'est bien un « string literal ».

Tu as répété cette partie là, mais cela ne suffit pas pour conclure
que le code posté par Jean-Marc est invalide. Tout au contraire --
parce que dans son code, il y a expansion, dans l'expansion, la chaîne
de caractère est substituée pour produire exactement ce qu'il contente
#include.


Si on fait la substitution textuelle. Traditionnellement.

Selon la norme, en revanche, même si c'est peut-être l'intention, ce
n'est pas ce qui est écrit dans le texte normatif.

C'est souvent difficile à savoir ce qui est l'intention, parce que la
norme est une oeuvre collective, et les individus peuvent avoir des
intentions différentes. Sans toujours s'en rendre compte. L'intention
des auteurs de l'exemple (non normatif) est clair -- pour eux, ça doit
fonctionner toujours « comme si » il y avait substitution textuelle.
Mais ce n'est pas certain que c'est l'intention des auteurs de la partie
normative.

Et pour revenir au posting initial : s'il y a substitution textuelle,
alors la technique originalement utilisée est aussi valable.

| Maintenant, lis §16.2 et montre-moi où elle dit qu'on a droit à un
| « string literal » dans un include. Tout ce que je vois là, ce sont des

Voir ci-dessus. Considère le code tel que posté par Jean-Marc

#define TARGET PS2
#define XSTR(x) #x
#define STR(x) XSTR(x)
#define PLATFILE(base,file) STR(base/TARGET/file)

#include PLATFILE(controller/src,Joypad.h)

Examinons pas à pas ce qui arrive à la dernière ligne:

(1) Elle est décomposée en suite de pp-tokens comme suit:

{#}{include} {PLATFILE}{(}{controller}{/}{src}{,}{Joypad.h}{)}

(2) À la phase de traduction 4:

Preprocessing directives are executed and macro invocations are
expanded. If a character sequence that matches the syntax of a
universal-character-name is produced by token concatenation
(16.3.3), the behavior is undefined. A #include preprocessing
directive causes the named header or source file to be processed
from phase 1 through phase 4, recursively.

Pour exécuter la directive ci-dessus, 16.2/4 ordonne:

A preprocessing directive of the form

# include pp-tokens new-line

(that does not match one of the two previous forms) is
permitted. The preprocessing tokens after include in the
directive are processed just as in normal text (each identifier
currently defined as a macro name is replaced by its replacement
list of preprocessing tokens). If the directive resulting after
all replacements does not match one of the two previous forms,
the behavior is undefined.140) The method by which a sequence of
preprocessing tokens between a < and a > preprocessing token
pair or a pair of " characters is combined into a single header
name preprocessing token is implementation-defined.


En effet. Ça donne l'impression que l'intention est qu'au moins dans
cette contexte, c'est une substitution textuelle qui est démandée. Et
que le résultat dépendrait toujours de l'implémentation. Le problème
reste, cependant, que le résultat de PLATFILE dans l'include est un
string literal, et qu'à string literal ne correspond à une des autres
formes d'inclusion que si on le considère comme texte, et non comme un
token. La forme de l'include en question veut trois tokens : un " (qui
n'est un token que dans cette contexte-ci), un q-char-sequence, et un ".
Je suis bien d'accord que la forme *textuelle* d'un string literal prète
à être interprété comme cette séquence, mais seulement si c'est le
texte, et non un token déjà fait.

(3) Nous sommes amenés à faire expansion de la macro fonctionnelle
PLATFILE. La sémantique est dictée par 16.3.1/1 :

After the arguments for the invocation of a function-like macro
have been identified, argument substitution takes place. A
parameter in the replacement list, unless preceded by a # or ##
preprocessing token or followed by a ## preprocessing token (see
below), is replaced by the corresponding argument after all macros
contained therein have been expanded. Before being substituted,
each argument s preprocessing tokens are completely macro replaced
as if they formed the rest of the translation unit; no other
preprocessing tokens are available.

Les arguments dans l'invocation de la macro fonctionnelle PLATFILE
sont:

(a) {controller}{/}{src} pour le paramètre base ;
(b) {Joypad.h} pour le paramètre file.

La liste de remplacement est {STR}{(}{base}{/}{TARGET}{/}{file}{)}.
Après susbtitution, nous avons :

{STR}{(}{controller}{/}{src}{/}{TARGET}{/}{Joypad.h}{)}

(4) Le traitement n'est pas fini, nous enseigne 16.3.4/1

After all parameters in the replacement list have been
substituted, the resulting preprocessing token sequence is
rescanned with all subsequent preprocessing tokens of the
source file for more macro names to replace.

Donc nous devons faire une expansion pour STR. Ce nous donne

{XSTR}{(}{controller}{/}{src}{/}{PS2}{/}{Joypad.h}{)}

car comme le demande 16.3.1/1, nous devons d'abord faire une
substitution de TARGET qui est utilisée dans l'argument pour STR

(5) La macro XSTR utilise l'opérateur # de stringification. Donc, nous
devons suivre les prescriptions de 16.3.2/2

If, in the replacement list, a parameter is immediately preceded
by a # preprocessing token, both are replaced by a single
character string literal preprocessing token that contains the
spelling of the preprocessing token sequence for the
corresponding argument. Each occurrence of white space between
the argument s preprocessing tokens becomes a single space
character in the character string literal. White space before
the first preprocessing token and after the last preprocessing
token comprising the argument is deleted. Otherwise, the
original spelling of each preprocessing token in the argument is
retained in the character string literal, except for special
handling for producing the spelling of string literals and
character literals: a character is inserted before each " and
character of a character literal or string literal (including
the delimiting " characters). If the replacement that results is
not a valid character string literal, the behavior is
undefined. The order of evaluation of # and ## operators is
unspecified.

Suivant ces préceptes, l'expansion de la macro XSTR pour l'argument
donné ci-dessus produit

"controller/src/PS2/Joypad.h"


Une fois de plus, tu es tombé dans le textuel. Le résultat, c'est un
token, qui est un string literal. Un seul token.

(6) Revenant à la directive #include PLATFILE(controller/src,Joypad.h)
à la base de ce long détour, après expansion de macro comme le
veut 16.2/4, elle donne

#include "controller/src/PS2/Joypad.h"

qui est bien de la forme scolaire dont tu parles juste ci-dessous


Le resultat, c'est :

# include string-literal

qui ne correspond à aucune des formes données. Il ne correspond que si
on considère la valeur du string-literal comme du texte, et qu'on le
rétokenise.

| q-char-sequence et des h-char-sequence. Et pour utiliser un
| q-char-sequence, il faut le mettre entre des "...".

Ben oui, et c'est bien ce que fait l'opérateur de stringification.
CQFD.


Ou est-ce que l'opérateur de stringification met des "..." ? L'opérateur
de stringification génère un token de type string literal. L'autre façon
d'obtenir un string literal, évidemment, c'est de mettre le texte entre
des "...". Sauf que dans le contexte d'un include, mettre le texte entre
des "..." ne fait pas un string literal.

C'est une distinction très important. Par exemple :

#include "a-b" // défini par l'implémentation,
// éventuellement illégal

char const a[] = "a-b" ; // défini par la norme.

La distinction est même très réele, dans des implémentations
existantes : sous Windows, "n" est bien une séquence de deux caractères
dans un q-char-sequence, mais non dans un string literal.

--
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
Gabriel Dos Reis
writes:

[...]

| C'est souvent difficile à savoir ce qui est l'intention, parce que la
| norme est une oeuvre collective, et les individus peuvent avoir des
| intentions différentes.

Et pour couronner le tout, il y a même des exemples dans le texte pour
expliquer comment les choses sont censées fonctionner -- auquel tu
aboutis aisément en suivant une démarche similaire à celle que j'ai
montré dans le long précédent message -- mais tu invoques des
principes vaseux pour passer à côté. Le texte que j'ai cité est celui
de C++, donc celui de C89. Mais le texte de C99 n'a pas changé sur ce
point et les exemples non plus. Mais ils sont tous faux selon James.
Groumph.

Il y a une question que j'ai posée et à laquelle tu as soigneusement
évité de répondre : peux-tu expliquer, texte à l'appui pourquoi tu
affirmes la version du posteur original est valide selon la norme ?

-- Gaby
Avatar
kanze
Gabriel Dos Reis wrote in message
news:...
writes:

[...]

| C'est souvent difficile à savoir ce qui est l'intention, parce que
| la norme est une oeuvre collective, et les individus peuvent avoir
| des intentions différentes.

Et pour couronner le tout, il y a même des exemples dans le texte pour
expliquer comment les choses sont censées fonctionner


Et il y a aussi une rationale, au moins pour C90. C'était un peu ce que
j'essayais à dire -- l'auteur de l'exemple n'avait pas forcement les
mêmes intentions que l'auteur du texte normatif.

Je t'accorde que la présence de l'exemple suggère qu'au moins certains
pensaient comme toi. N'empêche que le texte normatif est extrèmement
clair -- le préprocesseur travaille avec des tokens, non avec des
chaînes de caractères. Ce qui ne va pas sans poser des problèmes dans la
définition de l'opérateur # -- on est amené à parler de l'« orthographe
original », par exemple.

Je sais qu'à l'époque où la norme C était tout neuf, j'ai eu des
discussions avec certains membres du comité -- je ne me rappelle plus
qui, et que ce sont eux qui m'ont dit que la forme proposée par
Jean-Marc était illégal, et que la bonne forme était celui du premier
posting. C'est clair que « certains membres » ne font pas le comité
entier, et je ne prétendrais pas savoir ce que c'était les intentions du
comité pris dans l'ensemble, même en supposant qu'il y en a eu.

Ce qui m'est clair, c'est que le fait que le préprocesseur travaille sur
des tokens, et non textuellement, est une innovation du comité C. Et que
traditionnellement, les deux solutions marchent. Et que casser du code
sans raison, c'est un comportement infantin de la part d'un auteur du
compilateur.

--
James Kanze GABI Software http://www.gabi-soft.fr
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
Gabriel Dos Reis
writes:

| Gabriel Dos Reis wrote in message
| news:...
| > writes:
|
| > [...]
|
| > | C'est souvent difficile à savoir ce qui est l'intention, parce que
| > | la norme est une oeuvre collective, et les individus peuvent avoir
| > | des intentions différentes.
|
| > Et pour couronner le tout, il y a même des exemples dans le texte pour
| > expliquer comment les choses sont censées fonctionner
|
| Et il y a aussi une rationale, au moins pour C90. C'était un peu ce que
| j'essayais à dire -- l'auteur de l'exemple n'avait pas forcement les
| mêmes intentions que l'auteur du texte normatif.
|
| Je t'accorde que la présence de l'exemple suggère qu'au moins certains
| pensaient comme toi. N'empêche que le texte normatif est extrèmement
| clair -- le préprocesseur travaille avec des tokens, non avec des
| chaînes de caractères.

James --

Je ne prétend pas que le préprocesseur travaille avec des chaînes de
caractères. Si tu suis ce que j'ai écrit, j'ai pris soin de découper
en suite de pp-tokens et de manipuler ces suites de tokens, du début
jusqu'à la fin. L'opérateur # demande qu'on rassemble l' « original
spelling » des pp-tokens et que d'une manière ou d'une autre on en
produise un header-name. C'est le seul endroit où on a besoin de faire
la conversion. Ailleurs, on en a pas besoin et je ne l'ai pas fait.
Fais attention à ce que tu écris.

| Ce qui ne va pas sans poser des problèmes dans la
| définition de l'opérateur # -- on est amené à parler de l'« orthographe
| original », par exemple.
|
| Je sais qu'à l'époque où la norme C était tout neuf, j'ai eu des
| discussions avec certains membres du comité -- je ne me rappelle plus
| qui, et que ce sont eux qui m'ont dit que la forme proposée par
| Jean-Marc était illégal, et que la bonne forme était celui du premier
| posting.

Peux-tu articuler clairement pourquoi cette solution du premier
posting est valide ? C'est au moins la troisième fois que je pose la
question. Je l'ai posée dans le message auquel tu répondais aussi.

| C'est clair que « certains membres » ne font pas le comité
| entier, et je ne prétendrais pas savoir ce que c'était les intentions du
| comité pris dans l'ensemble, même en supposant qu'il y en a eu.
|
| Ce qui m'est clair, c'est que le fait que le préprocesseur travaille sur
| des tokens, et non textuellement, est une innovation du comité C. Et que
| traditionnellement, les deux solutions marchent. Et que casser du code
| sans raison, c'est un comportement infantin de la part d'un auteur du
| compilateur.

Ce qui est puérile, c'est de répéter que la solution du premier
posting est valide sans le montrer.

En réalité, la solution du premier posting est invalide ; donc elle ne
marche pas. On n'a pas pu le casser sans raison.

-- Gaby
Avatar
kanze
Gabriel Dos Reis wrote in message
news:...
writes:

| Gabriel Dos Reis wrote in message
| news:...
| > writes:

| > [...]

| > | C'est souvent difficile à savoir ce qui est l'intention, parce
| > | que la norme est une oeuvre collective, et les individus peuvent
| > | avoir des intentions différentes.

| > Et pour couronner le tout, il y a même des exemples dans le texte
| > pour expliquer comment les choses sont censées fonctionner

| Et il y a aussi une rationale, au moins pour C90. C'était un peu ce
| que j'essayais à dire -- l'auteur de l'exemple n'avait pas forcement
| les mêmes intentions que l'auteur du texte normatif.

| Je t'accorde que la présence de l'exemple suggère qu'au moins
| certains pensaient comme toi. N'empêche que le texte normatif est
| extrèmement clair -- le préprocesseur travaille avec des tokens, non
| avec des chaînes de caractères.

Je ne prétend pas que le préprocesseur travaille avec des chaînes de
caractères. Si tu suis ce que j'ai écrit, j'ai pris soin de découper
en suite de pp-tokens et de manipuler ces suites de tokens, du début
jusqu'à la fin. L'opérateur # demande qu'on rassemble l' « original
spelling » des pp-tokens et que d'une manière ou d'une autre on en
produise un header-name. C'est le seul endroit où on a besoin de faire
la conversion. Ailleurs, on en a pas besoin et je ne l'ai pas fait.
Fais attention à ce que tu écris.


C-à-d qu'à ce moment précis, il faut que le préprocesseur travaille avec
les chaînes de caractères.

L'utilisation de l'expression « original spelling » me fait hésiter, et
penser que peut-être tu as raison. N'empêche que je ne le trouve pas
clair -- le texte parle bien de renvoyer un « string literal », et il
n'y a aucune forme d'#include qui accepte un « string literal » -- en
tout cas, dans « #include "..." », il est certain qu'il n'y a aucun
string literal. Le problème reste -- l'expression « original spelling »
s'applique au texte qu'on stringize, et non au texte qu'on met
finalement dans l'expansion de l'include.

| Ce qui ne va pas sans poser des problèmes dans la définition de
| l'opérateur # -- on est amené à parler de l'« orthographe original
| », par exemple.

| Je sais qu'à l'époque où la norme C était tout neuf, j'ai eu des
| discussions avec certains membres du comité -- je ne me rappelle
| plus qui, et que ce sont eux qui m'ont dit que la forme proposée par
| Jean-Marc était illégal, et que la bonne forme était celui du
| premier posting.

Peux-tu articuler clairement pourquoi cette solution du premier
posting est valide ?


Je suis tempté à dire que c'est pour les mêmes raisons que la solution
que tu préconsises est valide. En fait, il n'y a rien dans la norme qui
l'interdit. Mais je crois que toi aussi, il y a des passages que tu as
sauté dans ce que j'ai écrit. Dans la contexte de l'expansion d'un
include, si une suite de caractères comme « x/y » est un token valid ou
non dépend de l'implémentation -- mais sous Unix, tous les compilateurs
que je connais (y compris g++ 3.4.0 dans le cas général) l'accepte comme
h-char-sequence (et dans la contexte d'une include, après un <, le
*seul* token légal du préprocesseur, c'est un h-char-sequence).

Note bien §2.1/3 : « The process of dividing a source file's characters
into preprocessing tokens is context-dependent. » On n'est pas n'importe
où, on est dans une contexte où le seul token du préprocesseur permis
est un h-char-sequence. Alors, si le résultat de ## n'est pas une
h-char-sequence valide, le code est illégal -- s'il l'est, le code est
légal. Évidemment, ce que c'est qu'un h-char-sequence dépend de
l'implémentation. Mais il doit être documenté, et il doit être pareil
partout. Si on accepte « #include <x/y> », on est obligé dans cette
contexte d'accepter x / y comme résultat de ##.


Il y a une deuxième chose qui me chagrine un peu, si on accepte ton
interprétation. Prenons l'exemple dans la norme, qui génère « #include
"vers2.h" ». Si j'ai bien compris ton argumentation, l'exemple serait
illégal si la version était « 2.2 », plutôt que « 2 », parce que le
résultat de ## dans l'expansion de INCFILE n'est pas un token légal du
préprocesseur (et la stringisation ne s'appliquera que plus tard).
D'abord, est-ce que j'ai bien compris ton argumentation, et que c'est
juste. Et deuxièmement, indépendamment de ce que dit la norme, est-ce
que c'est une contrainte raisonable ?

En autres mots, est-ce que la solution proposée par Jean-Marc doit
échouer si le nom du cible (TARGET) était PS-2, à la place de PS2 ?

(Et en passant, je crois que la version « correcte » de ce qu'il voulait
faire serait :

#define PATH_CONTROLLER_JOYPAD <controller##/##src##/##TARGET##/##Joypad.h>

On veut que ce qui se trouve entre les <...> soit un seul token. Mais ce
qui est « correcte » est académique -- ce qui importe dans la pratique,
évidemment, c'est ce qu'accepte les compilateurs.)

--
James Kanze GABI Software http://www.gabi-soft.fr
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
Gabriel Dos Reis
writes:

[...]

| > | Ce qui ne va pas sans poser des problèmes dans la définition de
| > | l'opérateur # -- on est amené à parler de l'« orthographe original
| > | », par exemple.
|
| > | Je sais qu'à l'époque où la norme C était tout neuf, j'ai eu des
| > | discussions avec certains membres du comité -- je ne me rappelle
| > | plus qui, et que ce sont eux qui m'ont dit que la forme proposée par
| > | Jean-Marc était illégal, et que la bonne forme était celui du
| > | premier posting.
|
| > Peux-tu articuler clairement pourquoi cette solution du premier
| > posting est valide ?
|
| Je suis tempté à dire que c'est pour les mêmes raisons que la solution
| que tu préconsises est valide.

Reprenons le code de départ.

#define PATH_CONTROLLER_JOYPAD <controller/src/##TARGET##/Joypad.h>
#include PATH_CONTROLLER_JOYPAD

La première ligne est une suite de pp-tokens comme suit :

{#}{define} {PATH_CONTROLLER_JOYPAD} {<}{controller}{/}{src}{/}{##}{TARGET}{##}{/}{Joypad}{.}{h}{>}


Le traitement de cette directive tombe sous le coup de 16.3.3/3 :

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 token
is concatenated 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.

Donc, avant même que la subtitution se produise (qui donnerait TARGET
-> PS2), la norme demande que {/} et {TARGET} soit concatenés, ce qui
donnerait {/TARGET} qui ne peut être un pp-otken valide.

| En fait, il n'y a rien dans la norme qui l'interdit.

Voir ci-dessus.

| Mais je crois que toi aussi, il y a des passages que tu as
| sauté dans ce que j'ai écrit. Dans la contexte de l'expansion d'un
| include, si une suite de caractères comme « x/y » est un token valid ou
| non dépend de l'implémentation -- mais sous Unix, tous les compilateurs
| que je connais (y compris g++ 3.4.0 dans le cas général) l'accepte comme
| h-char-sequence (et dans la contexte d'une include, après un <, le
| *seul* token légal du préprocesseur, c'est un h-char-sequence).

Je crois que tu confonds deux choses distinctes :

(1) ce que fait l'opérateur ## ; et
(2) et une suite de pp-tokens, comme {x}{/}{y}, dans un context
comme j'ai cité dans le précédent mail.

La norme demande explicitement que

#include <x/y>

soit pp-tokeniser comme

{#}{include} {<x/y>}

alors que

#define PATH <x/y>

sera pp-tokeniser comme

{#}{define} {PATH} {<}{x}{/}{y}{>}

| Note bien §2.1/3 : « The process of dividing a source file's characters
| into preprocessing tokens is context-dependent. » On n'est pas n'importe
| où, on est dans une contexte où le seul token du préprocesseur permis
| est un h-char-sequence. Alors, si le résultat de ## n'est pas une
| h-char-sequence valide, le code est illégal -- s'il l'est, le code est
| légal. Évidemment, ce que c'est qu'un h-char-sequence dépend de
| l'implémentation. Mais il doit être documenté, et il doit être pareil
| partout. Si on accepte « #include <x/y> », on est obligé dans cette
| contexte d'accepter x / y comme résultat de ##.

Je crois que tu vas trop vite dans ton raisonnement et que tu tombes
dan un trap. Par exemple, la ligne

#include <x/y>

est composée de

{#}{include} {<x/y>}

alors quelque chose comme

#define PATH <x/y>

donne

{#}{define} {PATH} {<}{x}{/}{y}{>}

c'est-à-dire le context de #include demane que si on voit '<' ou '"',
on prenne la plus longue chaîne possible pour faire un header-name.
Alors que dans le cas de #define, on tokenise et si on écrit quelque
chose

#include PATH

il y a expansion en

{#}{include} {<}{x}{/}{y}{>}

Note bien qu'ici, '<' apparaît comme un pp-token en soi -- et non
comme partie de chaîne de caractères qui ferait un header-name -- et
c'est seulement après cela que la norme demande que la suite de
pp-tokens entre les tokens {<} et {>} soit collectée pour former un
header-name (la méthode, i.e. comment c'est fabriqué dépend de
l'implémentation). Note bien, qu'il y a d'abord substitutions de
*pp-tokens* et après collection. Pendant cette substitution, il n'y a
pas de notion de h-char.


| Il y a une deuxième chose qui me chagrine un peu, si on accepte ton
| interprétation. Prenons l'exemple dans la norme, qui génère « #include
| "vers2.h" ». Si j'ai bien compris ton argumentation, l'exemple serait
| illégal si la version était « 2.2 », plutôt que « 2 », parce que le
| résultat de ## dans l'expansion de INCFILE n'est pas un token légal du
| préprocesseur (et la stringisation ne s'appliquera que plus tard).


Reproduisons l'exemple de la norme pour voir de quoi on parle.
16.3.5/6

#define str(s) # s
#define xstr(s) str(s)
#define debug(s, t) printf("x" # s "= %d, x" # t "= %s",
x ## s, x ## t)
#define INCFILE(n) vers ## n /* from previous #include example */
#define glue(a, b) a ## b
#define xglue(a, b) glue(a, b)
#define HIGHLOW "hello"
#define LOW LOW ", world"

debug(1, 2);
fputs(str(strncmp("abcd", "abc", 4 ) /* this goes away */
== 0) str(: @n), s);
#include xstr(INCFILE(2).h)
glue(HIGH, LOW);
xglue(HIGH, LOW)


Si tu écris INCFILE(2.2) au lieu de INCFILE(2), tu tombes sous le coup
de 16.3.3/2

If, in the replacement list, a parameter is immediately preceded
or followed by a ## preprocessing token, the parameter is replaced
by the corresponding argument s preprocessing token sequence.

Donc, l'expansion de la macro INCFILE donne

{vers}{##}{2.2}

ce qui après concaténation ressemble à {vers2.2} qui n'est pas un
pp-token valide.

Note bien que l'exemple de la norme n'est pas

#include xstr(INCFILE(2.h))

qui produirait un pp-token invalde lors de la concaténation, mais bien

#include xstr(INCFILE(2).h)

de sorte la concatération se passe bien et la stringification fasse
proprement ce qu'il doit faire.


| D'abord, est-ce que j'ai bien compris ton argumentation, et que c'est
| juste.

Oui.

| Et deuxièmement, indépendamment de ce que dit la norme, est-ce
| que c'est une contrainte raisonable ?

De mon point ce vue, cela facilite le traitement -- si on se base sur
une subtsitution de tokens. (Si on fait une substituion sur du
texte, cela n'est pas nécessaire).

Concrêtement, l'idée est qu'on prend deux tokens et on les remplace
par un autre. On peut imaginer une routine dont le propos est de
valider une suite de caractères comme un pp-token ou non. Donc on
prend les deux tokens, on assemble leurs orthographes et on demande à
la routine si c'est un pp-token ou non. C'est simple, stupide et
efficace. L'autre alternative serait de redemander un redécoupage en
suite de tokens -- c'est le procédé traditionnel, par exemple gcc
-traditional.

| En autres mots, est-ce que la solution proposée par Jean-Marc doit
| échouer si le nom du cible (TARGET) était PS-2, à la place de PS2 ?

Non, la solution proposée par Jean-Marc marche même si TARGET était
PS-2 à la place de PS2. La raison est qu'il n'utilise pas l'opérateur
## qui pose de problème dans le cas de INCFILE(2.2).

| (Et en passant, je crois que la version « correcte » de ce qu'il voulait
| faire serait :
|
| #define PATH_CONTROLLER_JOYPAD <controller##/##src##/##TARGET##/##Joypad.h>

Non, ce n'est pas correcte pour la même raison qu'avant : quand tu
concatènes {controller} et {/}, tu n'obtiens pas un pp-token valide.

-- Gaby
Avatar
Falk Tannhäuser
wrote:
Ou est-ce que l'opérateur de stringification met des "..." ? L'opérateur
de stringification génère un token de type string literal. L'autre façon
d'obtenir un string literal, évidemment, c'est de mettre le texte entre
des "...". Sauf que dans le contexte d'un include, mettre le texte entre
des "..." ne fait pas un string literal.

C'est une distinction très importante. Par exemple :

#include "a-b" // défini par l'implémentation,
// éventuellement illégal

char const a[] = "a-b" ; // défini par la norme.

La distinction est même très réele, dans des implémentations
existantes : sous Windows, "n" est bien une séquence de deux caractères
dans un q-char-sequence, mais non dans un string literal.


Effectivement. Les linges suivantes compilent chez moi avec gcc 3.3.1 CygWin,
à condition que le fichier "c:tbn.h" existe :

#define STR(x) #x
#define X STR(c:tbn.h)
#include X
char const c[] = X;

(sizeof c == 9)
Je ne suis pas sûr que cela soit le comportement mandaté par 16.3.2/2 et
16.2/4+3

Falk

1 2 3 4