Como concatenar duas vezes com o pré-processador C e expandir uma macro como em "arg ## _ ## MACRO"?

152

Eu estou tentando escrever um programa onde os nomes de algumas funções dependem do valor de uma determinada variável de macro com uma macro como esta:

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);

Infelizmente, a macro NAME()transforma isso em

int some_function_VARIABLE(int a);

ao invés de

int some_function_3(int a);

Portanto, esse é claramente o caminho errado. Felizmente, o número de diferentes valores possíveis para VARIABLE é pequeno, então eu posso simplesmente fazer #if VARIABLE == ne listar todos os casos separadamente, mas fiquei imaginando se existe uma maneira inteligente de fazer isso.

JJ.
fonte
3
Tem certeza de que não deseja usar ponteiros de função?
György Andrasek 29/09/09
8
@Jurily - Os ponteiros de função funcionam em tempo de execução, o pré-processador funciona em (antes) o tempo de compilação. Há uma diferença, mesmo que ambos possam ser usados ​​para a mesma tarefa.
31711 Chris Lutz
1
O ponto é que o que é usado é uma biblioteca de geometria computacional rápida .. que é conectada para uma determinada dimensão. No entanto, às vezes alguém gostaria de usá-lo com algumas dimensões diferentes (por exemplo, 2 e 3) e, portanto, seria necessário um meio fácil de gerar código com nomes de funções e tipos dependentes de dimensão. Além disso, o código é escrito em ANSI C, portanto o material C ++ com modelos e especialização não é aplicável aqui.
JJ.
2
A votação para reabrir porque esta pergunta é específica sobre expansão recursiva de macro e stackoverflow.com/questions/216875/using-in-macros é um genérico "para que serve". O título desta pergunta deve ser mais preciso.
Ciro Santilli adicionou uma nova foto
Eu gostaria que este exemplo tivesse sido minimizado: o mesmo acontece #define A 0 \n #define M a ## A: ter dois## não é a chave.
Ciro Santilli escreveu

Respostas:

223

Pré-processador C padrão

$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y)  PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)

extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"





extern void mine_3(char *x);
$

Dois níveis de indireção

Em comentário a outra resposta, o Cade Roux perguntou por que isso precisa de dois níveis de indireção. A resposta irreverente é porque é assim que o padrão exige que ele funcione; você tende a achar que também precisa do truque equivalente com o operador de stringing.

A seção 6.10.3 da norma C99 abrange a 'substituição de macro' e a 6.10.3.1 abrange a 'substituição de argumento'.

Após a identificação dos argumentos para a invocação de uma macro semelhante à função, ocorre a substituição de argumentos. Um parâmetro na lista de substituição, a menos que seja precedido por um token de pré #- ##processamento ou seguido por um ##token de pré - processamento (veja abaixo), é substituído pelo argumento correspondente depois que todas as macros nele contidas foram expandidas. Antes de serem substituídos, os tokens de pré-processamento de cada argumento são completamente substituídos por macro, como se tivessem formado o restante do arquivo de pré-processamento; nenhum outro tokens de pré-processamento está disponível.

Na invocação NAME(mine) , o argumento é 'meu'; é totalmente expandido para 'meu'; é então substituído na cadeia de substituição:

EVALUATOR(mine, VARIABLE)

Agora, a macro EVALUATOR é descoberta e os argumentos são isolados como 'mine' e 'VARIABLE'; o último é totalmente expandido para '3' e substituído na string de substituição:

PASTER(mine, 3)

A operação disso é coberta por outras regras (6.10.3.3 'O operador ##'):

Se, na lista de substituição de uma macro semelhante à função, um parâmetro for imediatamente precedido ou seguido por um ## token de pré processamento, o parâmetro será substituído pela sequência de token de pré-processamento do argumento correspondente; [...]

Para chamadas de macro semelhantes a objeto e a função, antes que a lista de substituição seja reexaminada para que mais nomes de macro sejam substituídos, cada instância de um ## token de pré processamento na lista de substituição (não de um argumento) é excluída e o token de pré-processamento anterior é concatenado com o seguinte token de pré-processamento.

Portanto, a lista de substituição contém xseguido por ##e também ##seguido por y; então nós temos:

mine ## _ ## 3

e eliminar os ##tokens e concatenar os tokens de ambos os lados combina 'mine' com '_' e '3' para produzir:

mine_3

Este é o resultado desejado.


Se olharmos para a pergunta original, o código foi (adaptado para usar 'mine' em vez de 'some_function'):

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

NAME(mine)

O argumento para NAME é claramente 'meu' e é totalmente expandido.
Seguindo as regras de 6.10.3.3, encontramos:

mine ## _ ## VARIABLE

que, quando os ##operadores são eliminados, mapeia para:

mine_VARIABLE

exatamente como relatado na pergunta.


Pré-processador C tradicional

Robert Rüger pergunta :

Existe alguma maneira de fazer isso com o pré-processador C tradicional que não possui o operador de colagem de token ##?

Talvez, e talvez não - isso depende do pré-processador. Uma das vantagens do pré-processador padrão é que ele possui esse recurso que funciona de maneira confiável, ao passo que houve implementações diferentes para pré-processadores pré-padrão. Um requisito é que, quando o pré-processador substitui um comentário, ele não gera um espaço como o pré-processador ANSI deve fazer. O pré-processador C do GCC (6.3.0) C atende a esse requisito; o pré-processador Clang do XCode 8.2.1 não.

Quando funciona, isso faz o trabalho ( x-paste.c):

#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

Observe que não há um espaço entre fun,e VARIABLE- isso é importante porque, se presente, ele é copiado para a saída e você acaba com mine_ 3o nome, que não é sintaticamente válido, é claro. (Agora, por favor, posso recuperar meu cabelo?)

Com o GCC 6.3.0 (em execução cpp -traditional x-paste.c), recebo:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);

Com o Clang do XCode 8.2.1, recebo:

# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2





extern void mine _ 3(char *x);

Esses espaços estragam tudo. Noto que ambos os pré-processadores estão corretos; diferentes pré-processadores pré-padrão exibiram os dois comportamentos, o que tornou a colagem de tokens um processo extremamente irritante e não confiável ao tentar portar código. O padrão com a ##notação simplifica radicalmente isso.

Pode haver outras maneiras de fazer isso. Entretanto, isso não funciona:

#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

O GCC gera:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

Perto, mas sem dados. YMMV, é claro, dependendo do pré-processador pré-padrão que você está usando. Francamente, se você está preso a um pré-processador que não está cooperando, provavelmente seria mais simples usar um pré-processador C padrão no lugar do pré-padrão (geralmente existe uma maneira de configurar o compilador adequadamente) do que gaste muito tempo tentando descobrir uma maneira de fazer o trabalho.

Jonathan Leffler
fonte
1
Sim, isso resolve o problema. Eu conhecia o truque com dois níveis de recursão - eu tinha que jogar com estringificação pelo menos uma vez - mas não sabia como fazer esse.
JJ.
Existe alguma maneira de fazer isso com o pré-processador C tradicional, que não possui o operador de colagem de token ##?
Robert Rüger
1
@ RobertRüger: dobra o comprimento da resposta, mas adicionei informações para cobrir cpp -traditional. Observe que não há uma resposta definitiva - depende do pré-processador que você possui.
Jonathan Leffler
Muito obrigado pela resposta. Isso é totalmente ótimo! Enquanto isso, eu também encontrei outra solução ligeiramente diferente. Veja aqui . Ele também tem o problema de não funcionar com o clang. Felizmente, isso não é um problema para o meu aplicativo ...
Robert Rüger
32
#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

Honestamente, você não quer saber por que isso funciona. Se você sabe por que isso funciona, você se tornará o cara no trabalho que conhece esse tipo de coisa, e todos virão fazer perguntas. =)

Edit: se você realmente quer saber por que ele funciona, eu felizmente postarei uma explicação, assumindo que ninguém me bate nele.

Stephen Canon
fonte
Você poderia explicar por que ele precisa de dois níveis de indireção. Eu tive uma resposta com um nível de redirecionamento, mas excluí a resposta porque precisava instalar o C ++ no meu Visual Studio e, em seguida, não funcionou.
Cade Roux