Vírgula na macro C / C ++

103

Digamos que temos uma macro como esta

#define FOO(type,name) type name

Que poderíamos usar como

FOO(int, int_var);

Mas nem sempre tão simplesmente:

FOO(std::map<int, int>, map_var); // error: macro "FOO" passed 3 arguments, but takes just 2

Claro que poderíamos fazer:

 typedef std::map<int, int> map_int_int_t;
 FOO(map_int_int_t, map_var); // OK

o que não é muito ergonômico. As incompatibilidades do tipo Plus devem ser tratadas. Alguma ideia de como resolver isso com macro?

PoP
fonte
Estou supondo que você precisa escapar caracteres com um significado para torná-los literais.
Jite
Pelo menos em C ++, você pode colocar um typedef em qualquer lugar, então não tenho certeza por que você diz que tem que ser "de antemão".
Vaughn Cato

Respostas:

108

Porque colchetes também pode representar (ou ocorrem em) os operadores de comparação <, >, <=e >=, a expansão macro não pode ignorar vírgulas dentro de colchetes como ele faz entre parênteses. (Isso também é um problema para colchetes e colchetes, embora eles geralmente ocorram como pares equilibrados.) Você pode colocar o argumento da macro entre parênteses:

FOO((std::map<int, int>), map_var);

O problema é então que o parâmetro permanece entre parênteses dentro da expansão da macro, o que impede que seja lido como um tipo na maioria dos contextos.

Um bom truque para contornar isso é que em C ++, você pode extrair um nome de tipo de um nome de tipo entre parênteses usando um tipo de função:

template<typename T> struct argument_type;
template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
#define FOO(t,name) argument_type<void(t)>::type name
FOO((std::map<int, int>), map_var);

Como a formação de tipos de função ignora parênteses extras, você pode usar esta macro com ou sem parênteses, onde o nome do tipo não inclui uma vírgula:

FOO((int), int_var);
FOO(int, int_var2);

Em C, é claro, isso não é necessário porque os nomes dos tipos não podem conter vírgulas fora dos parênteses. Portanto, para uma macro de linguagem cruzada, você pode escrever:

#ifdef __cplusplus__
template<typename T> struct argument_type;
template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
#define FOO(t,name) argument_type<void(t)>::type name
#else
#define FOO(t,name) t name
#endif
ecatmur
fonte
Isso é incrível. Mas como você descobriu isso? Tenho tentado vários truques e nunca pensei que um tipo de função resolveria o problema.
Will Custode
@WilliamCustode, pelo que me lembro, estive estudando a gramática dos tipos de função e das declarações de função com referência ao problema de análise mais incômodo, então foi fortuito que eu estivesse ciente de que parênteses redundantes poderiam ser aplicados a um tipo naquele contexto.
ecatmur
Encontrei um problema com este método ao trabalhar com modelos. Digamos que o código que eu queria era este: template<class KeyType, class ValueType> void SomeFunc(FOO(std::map<KeyType, ValueType>) element) {}Se eu aplicar esta solução aqui, as estruturas por trás da macro se tornam tipos dependentes e o prefixo do nome de tipo agora é necessário no tipo. Você pode adicioná-lo, mas a dedução de tipo foi quebrada, então agora você tem que listar manualmente os argumentos de tipo para chamar a função. Acabei usando o método do temple de definir uma macro para a vírgula. Pode não parecer tão bonito, mas funcionou perfeitamente.
Roger Sanders
Um pequeno problema na resposta: Afirma que vírgulas são ignoradas por dentro [] e {}, não são, só funciona com ()tristeza. Veja: No entanto, não há necessidade de colchetes ou colchetes para equilibrar ...
VinGarcia
Infelizmente, isso não funciona no MSVC: godbolt.org/z/WPjYW8 . Parece que o MSVC não permite adicionar vários parênteses e não consegue analisá-los. Uma solução que não é tão elegante, mas mais rápido (instantiations modelo menos) é envolver o argumento vírgula-ed em um macro de fardos: #define PROTECT(...) argument_type<void(__VA_ARGS__)>::type. Passar argumentos agora é facilmente possível, mesmo por meio de várias macros e, para tipos simples, você pode omitir PROTECT. No entanto, os tipos de função tornam-se ponteiros de função quando avaliados desta forma
Flamefire
119

Se você não pode usar parênteses e não gosta da solução SINGLE_ARG de Mike, basta definir uma COMMA:

#define COMMA ,

FOO(std::map<int COMMA int>, map_var);

Isso também ajuda se você deseja sequenciar alguns dos argumentos da macro, como em

#include <cstdio>
#include <map>
#include <typeinfo>

#define STRV(...) #__VA_ARGS__
#define COMMA ,
#define FOO(type, bar) bar(STRV(type) \
    " has typeid name \"%s\"", typeid(type).name())

int main()
{
    FOO(std::map<int COMMA int>, std::printf);
}

que imprime std::map<int , int> has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE".

não um usuário
fonte
16
#define COMMA uau, você acabou de me economizar HORAS de trabalho ... por que não pensei nisso anos atrás. Obrigado por compartilhar essa idéia. Isso está me permitindo até mesmo construir macros que configuram funções com diferentes contagens de argumentos.
moliad
28
Mais 1 para o terror
nomezero
1
@kiw Se você #define STRVX(...) STRV(__VA_ARGS__)e #define STRV(...) # __VA_ARGS__, std::cout << STRV(type<A COMMA B>) << std::endl;imprimirá type<A COMMA B>e std::cout << STRVX(type<A COMMA B>) << std::endl;imprimirá type<A , B>. ( STRVé para "stringify variadic" e STRVXé para "stringify variadic expandida".)
não é um usuário
1
@ not-a-user sim, mas com macros variáveis ​​você não precisa da COMMAmacro em primeiro lugar. Foi com isso que eu terminei.
kiw
Eu nunca usaria isso, mas +1 por ser hilário.
Rafael Baptista
58

Se o seu pré-processador for compatível com macros variadas:

#define SINGLE_ARG(...) __VA_ARGS__
#define FOO(type,name) type name

FOO(SINGLE_ARG(std::map<int, int>), map_var);

Caso contrário, é um pouco mais tedioso:

#define SINGLE_ARG2(A,B) A,B
#define SINGLE_ARG3(A,B,C) A,B,C
// as many as you'll need

FOO(SINGLE_ARG2(std::map<int, int>), map_var);
Mike Seymour
fonte
Oh, Deus ... Por quê? Por que não apenas colocar entre parênteses?
15
@VladLazarenko: Porque você nem sempre pode colocar pedaços de código arbitrários entre parênteses. Em particular, você não pode colocar parênteses ao redor do nome do tipo em um declarador, que é exatamente o que esse argumento se torna.
Mike Seymour
2
... e também porque você só poderá modificar a definição da macro e não todos os lugares que a chamam (que podem não estar sob seu controle ou podem estar espalhados por milhares de arquivos, etc). Isso ocorre, por exemplo, ao adicionar uma macro para assumir funções de uma função com nome semelhante.
BeeOnRope de
32

Basta definir FOOcomo

#define UNPACK( ... ) __VA_ARGS__

#define FOO( type, name ) UNPACK type name

Em seguida, invoque-o sempre com parênteses em torno do argumento de tipo, por exemplo

FOO( (std::map<int, int>), map_var );

É claro que pode ser uma boa ideia exemplificar as invocações em um comentário sobre a definição da macro.

Saúde e hth. - Alf
fonte
Não sei por que isso está tão baixo, é uma solução muito melhor do que Mike Seymours. É rápido, simples e completamente escondido do usuário.
iFreilicht
3
@iFreilicht: Foi postado pouco mais de um ano depois. ;-)
Saúde e hth. - Alf
5
E porque também é difícil entender como e por que funciona
VinGarcia
@VinGarcia, você pode explicar por que / como funciona? Por que os parênteses são necessários ao chamá-lo? O que UNPACKfazer quando usado assim ) UNPACK type name? Por que typeobtém o tipo corretamente quando usado ) UNPACK type name? O que diabos está acontecendo aqui?
usuário
Não @user, talvez Cheers e hth possam responder
VinGarcia
4

Existem pelo menos duas maneiras de fazer isso. Primeiro, você pode definir uma macro que leva vários argumentos:

#define FOO2(type1, type2, name) type1, type2, name

se você fizer isso, poderá acabar definindo mais macros para lidar com mais argumentos.

Em segundo lugar, você pode colocar parênteses em torno do argumento:

#define FOO(type, name) type name
F00((std::map<int, int>) map_var;

se você fizer isso, poderá descobrir que os parênteses extras bagunçam a sintaxe do resultado.

Pete Becker
fonte
Para a primeira solução, cada macro deverá ter um nome diferente, pois as macros não se sobrecarregam. E para o segundo, se você estiver passando um nome de tipo, há uma boa chance de que ele seja usado para declarar uma variável (ou um typedef), então os parênteses causarão problemas.
James Kanze
4

Isso é possível com P99 :

#include "p99/p99.h"
#define FOO(...) P99_ALLBUTLAST(__VA_ARGS__) P99_LAST(__VA_ARGS__)
FOO()

O código acima remove efetivamente apenas a última vírgula da lista de argumentos. Verifique com clang -E(P99 requer um compilador C99).

xiaq
fonte
3

A resposta simples é que você não pode. Este é um efeito colateral da escolha de <...>argumentos de modelo; o <e >também aparecem em contextos desequilibrados, de forma que o mecanismo da macro não pode ser estendido para tratá-los como faz com os parênteses. (Alguns dos membros do comitê defenderam um token diferente, digamos (^...^), mas não conseguiram convencer a maioria dos problemas de uso <...>.)

James Kanze
fonte
2
(^...^)esta é uma cara feliz :)
CygnusX1