2012-02-07 29 views
21

Así que tengo una macro que funciona muy bien en GCC, pero no en el compilador C++ de Microsoft. Espero que alguien sepa de una solución, o quizás pueda explicarme por qué se comporta de esta manera.MSVC++ variadic macro expansion

Estoy seguro de que este macro no es exactamente "estándar", pero realmente me ayudaría.

Aquí está un ejemplo funcional de la macro:

#define VA_NARGS_IMPL(_1, _2, _3, _4, _5, N, ...) N 
#define VA_NARGS(...) VA_NARGS_IMPL(__VA_ARGS__, 5, 4, 3, 2, 1) 

#define FULLY_EXPANDED(count, ...) \ 
    MAC## count (__VA_ARGS__) 

#define SEMI_EXPANDED(count, ...) FULLY_EXPANDED(count, __VA_ARGS__) 

#define EXPAND_THESE(...) SEMI_EXPANDED(VA_NARGS(__VA_ARGS__), __VA_ARGS__) 

#define ACTUAL_MACRO(x) parent->GetProperty<x>(); 
#define MAC1(a) ACTUAL_MACRO(a) 
#define MAC2(a,b) MAC1(a) ACTUAL_MACRO(b) 
#define MAC3(a,b,c) MAC2(a,b) ACTUAL_MACRO(c) 
#define MAC4(a,b,c,d) MAC3(a,b,c) ACTUAL_MACRO(d) 
#define MAC5(a,b,c,d,e) MAC4(a,b,c,d) ACTUAL_MACRO(e) 

Aquí es cómo podría utilizar esta macro:

struct MyStructure 
{ 
    void Foo() 
    { 
    EXPAND_THESE(Property1, Property2, Property3, Property4) 
    } 

    Base * parent; 
} 

está aquí cómo GCC se expande lo anterior:

struct MyStructure 
{ 
    void Foo() 
    { 
    parent->GetProperty<Property1>(); 
    parent->GetProperty<Property2>(); 
    parent->GetProperty<Property3>(); 
    parent->GetProperty<Property4>(); 
    } 

    Base * parent; 
} 

Pero Microsoft por alguna razón expande todos mis __VA_ARGS__ como un argumento:

struct MyStructure 
{ 
    void Foo() 
    { 
    parent->GetProperty<Property1, Property2, Property3, Property4>(); 
    } 

    Base * parent; 
} 

¿Alguien sabe por qué es esto? ¿Hay algún truco que pueda utilizar para que Microsoft expanda esto como GCC? ¿Tal vez tirar un par de paréntesis adicionales?

Este tipo de macros me podría ayudar a reemplazar un montón de código "pegamento", pero debido a este problema, no puedo moverlo a mi proyecto VS. ¡Cualquier ayuda sería muy apreciada!

Gracias.

+5

'SA [bug] (http://connect.microsoft.com/VisualStudio/feedback/details/380090/variadic-macro-replacement) y I don' t piensan planean arreglarlo pronto. –

+0

Duplicado vinculado: [¿Cómo corregir problemas relacionados con la macro variadic con "macro sobrecarga" en MSVC++ (estudio de Microsoft Visual)?] (Https://stackoverflow.com/q/48710758/514235) - @JesseGood Thx para señalar el error. – iammilind

Respuesta

17

Casualmente, me encontré con este problema solo hoy, y después de suficiente esfuerzo creo que he encontrado una solución para mis propios fines. El error es que MSVC trata __VA_ARGS__ como un único token en las listas de argumentos. Pero puede evitar esto al no usarlo directamente dentro de una lista de argumentos de macro llamada. This comment sugiere el inicio de una respuesta a sus problemas:

#define VA_NARGS(...) VA_NUM_ARGS_IMPL_((__VA_ARGS__, 5,4,3,2,1)) 
#define VA_NARGS_IMPL_(tuple) VA_NUM_ARGS_IMPL tuple 
#define VA_NARGS_IMPL(_1,_2,_3,_4,_5,N,...) N 

Pero entonces sospecho que es probable que se encuentra con el problema de asegurarse de que obtiene totalmente expandido a la actual "N" que desea, y no a VA_NARGS_IMPL (arg1, arg2, 5, 4, 3, 2, 1) decir. Descubrí que mi código (que se parecía al suyo) tuvo que cambiar para expandir MAC##code, todo como una unidad, y luego tenía que combinarse por separado con la lista de argumentos. Aquí está el código que he encontrado trabajado para mí:

#define ASSERT_HELPER1(expr) singleArgumentExpansion(expr) 
#define ASSERT_HELPER2(expr, explain) \ 
    twoArgumentExpansion(expr, explain) 

/* 
* Count the number of arguments passed to ASSERT, very carefully 
* tiptoeing around an MSVC bug where it improperly expands __VA_ARGS__ as a 
* single token in argument lists. See these URLs for details: 
* 
* http://connect.microsoft.com/VisualStudio/feedback/details/380090/variadic-macro-replacement 
* http://cplusplus.co.il/2010/07/17/variadic-macro-to-count-number-of-arguments/#comment-644 
*/ 
#define COUNT_ASSERT_ARGS_IMPL2(_1, _2, count, ...) \ 
    count 
#define COUNT_ASSERT_ARGS_IMPL(args) \ 
    COUNT_ASSERT_ARGS_IMPL2 args 
#define COUNT_ASSERT_ARGS(...) \ 
    COUNT_ASSERT_ARGS_IMPL((__VA_ARGS__, 2, 1, 0)) 
/* Pick the right helper macro to invoke. */ 
#define ASSERT_CHOOSE_HELPER2(count) ASSERT_HELPER##count 
#define ASSERT_CHOOSE_HELPER1(count) ASSERT_CHOOSE_HELPER2(count) 
#define ASSERT_CHOOSE_HELPER(count) ASSERT_CHOOSE_HELPER1(count) 
/* The actual macro. */ 
#define ASSERT_GLUE(x, y) x y 
#define ASSERT(...) \ 
    ASSERT_GLUE(ASSERT_CHOOSE_HELPER(COUNT_ASSERT_ARGS(__VA_ARGS__)), \ 
       (__VA_ARGS__)) 

int foo() 
{ 
    ASSERT(one); // singleArgumentExpansion(one) 
    ASSERT(two, "foopy"); // twoArgumentExpansion(two, "foopy") 
} 

Mi mente es demasiado papilla después de unas horas para resolver mis propios problemas para luego ir y completamente resolver la suya, lo siento decir. :-) Pero creo que esto es suficiente para que hagas algo que funcione, con un poco de trabajo.

16

Sé que esta pregunta tiene más de dos años, pero pensé que trataría de dar una respuesta más refinada a los que todavía tropiezan con esto, como yo.

La respuesta de Jeff Walden funciona y todo, pero tiene que declarar FOO_CHOOSE_HELPER/1/2 para cada macro de FOO que desee tener argumentos variados. He desarrollado una capa de abstracción para resolver este problema.Considere lo siguiente:

#define GLUE(x, y) x y 

#define RETURN_ARG_COUNT(_1_, _2_, _3_, _4_, _5_, count, ...) count 
#define EXPAND_ARGS(args) RETURN_ARG_COUNT args 
#define COUNT_ARGS_MAX5(...) EXPAND_ARGS((__VA_ARGS__, 5, 4, 3, 2, 1, 0)) 

#define OVERLOAD_MACRO2(name, count) name##count 
#define OVERLOAD_MACRO1(name, count) OVERLOAD_MACRO2(name, count) 
#define OVERLOAD_MACRO(name, count) OVERLOAD_MACRO1(name, count) 

#define CALL_OVERLOAD(name, ...) GLUE(OVERLOAD_MACRO(name, COUNT_ARGS_MAX5(__VA_ARGS__)), (__VA_ARGS__)) 

Con esta arquitectura se puede definir macros variadic como tal:

#define ERROR1(title) printf("Error: %s\n", title) 
#define ERROR2(title, message)\ 
    ERROR1(title);\ 
    printf("Message: %s\n", message) 
#define ERROR(...) CALL_OVERLOAD(ERROR, __VA_ARGS__) 

#define ASSERT1(expr) singleArgumentExpansion(expr) 
#define ASSERT2(expr, explain) twoArgumentExpansion(expr, explain) 
#define ASSERT(...) CALL_OVERLOAD(ASSERT, __VA_ARGS__) 

con la respuesta de Jeff tendría que definir las macros de la siguiente manera:

#define ERROR1(title) printf("Error: %s\n", title) 
#define ERROR2(title, message)\ 
    ERROR1(title);\ 
    printf("Message: %s\n", message) 

#define ERROR_CHOOSE_HELPER2(count) ERROR##count 
#define ERROR_CHOOSE_HELPER1(count) ERROR_CHOOSE_HELPER2(count) 
#define ERROR_CHOOSE_HELPER(count) ERROR_CHOOSE_HELPER1(count) 

#define ERROR(...) GLUE(ERROR_CHOOSE_HELPER(COUNT_ARGS_MAX5(__VA_ARGS__)),\ 
    (__VA_ARGS__)) 

#define ASSERT1(expr) singleArgumentExpansion(expr) 
#define ASSERT2(expr, explain) twoArgumentExpansion(expr, explain) 

#define ASSERT_CHOOSE_HELPER2(count) ASSERT##count 
#define ASSERT_CHOOSE_HELPER1(count) ASSERT_CHOOSE_HELPER2(count) 
#define ASSERT_CHOOSE_HELPER(count) ASSERT_CHOOSE_HELPER1(count) 

#define ASSERT(...) GLUE(ASSERT_CHOOSE_HELPER(COUNT_ARGS_MAX5(__VA_ARGS__)),\ 
    (__VA_ARGS__)) 

Es no es gran cosa, sin embargo, me gusta que mi código sea lo más conciso posible. También ayuda de forma exponencial, si está utilizando varias macros variadas, para reducir la duplicación de código y las complicaciones que pueden causar. Hasta donde yo sé, este método también es portátil. Lo probé en muchos de los compiladores más comunes y produjeron los mismos resultados.

Ejemplo uso:

int foo() 
{ 
    ASSERT(one); // singleArgumentExpansion(one) 
    ASSERT(two, "foopy"); // twoArgumentExpansion(two, "foopy") 

    ERROR("Only print a title"); 
    ERROR("Error title", "Extended error description"); 
} 
+0

Nota, necesitaba eliminar el ';' al final de '#define CALL_OVERLOAD' o recibo un error' error: expected ')' before ';' token' con gcc4.9 – ideasman42

+0

En función de esto, hay un ejemplo que usa hasta 16 args para implementar una macro ELEM basada en var-args, http://stackoverflow.com/a/24837037/432509 (puede ser de interés) – ideasman42

+0

@ ideasman42 Gracias por señalarlo, lo actualicé. –