¿Pueden las macros estar sobrecargadas por el número de argumentos?

¿Cómo funciona esto ? ¿Cómo se puede implementar una macro variadica C99 / C ++ 11 para expandirse a diferentes cosas sobre la única base de cuántos argumentos se le otorgan?

(Editar: Consulte el final para obtener una solución ya hecha).

Para obtener una macro sobrecargada, primero necesitamos una macro que seleccione entre varias implementaciones. Esta parte no usa una macro variadica. Entonces, una macro variadica que cuenta genéricamente sus argumentos produce un selector. Al conectar el recuento de argumentos en un despachador, se produce una macro sobrecargada.

Advertencia: Este sistema no puede diferenciar entre cero y un argumento porque no hay diferencia entre ningún argumento y un solo argumento vacío. Ambos se ven como MACRO() .


Para seleccionar entre implementaciones, use el operador de macrocatenas con una serie de macros similares a funciones.

 #define select( selector, ... ) impl ## _ ## selector( __VA_ARGS__ ) #define impl_1() meh #define impl_2( abc, xyz ) # abc "wizza" xyz() //etc // usage: select( 1 ) => impl_1() => meh // select( 2, huz, bar ) => impl_2( huzza, bar ) => "huz" "wizza" bar() 

Como el operador ## suprime la macroexpansión de sus argumentos, es mejor incluirlo en otra macro.

 #define CAT( A, B ) A ## B #define SELECT( NAME, NUM ) CAT( NAME ## _, NUM ) 

Para contar argumentos, use __VA_ARGS__ para cambiar los argumentos como tal (esta es la parte inteligente):

 #define GET_COUNT( _1, _2, _3, _4, _5, _6 /* ad nauseam */, COUNT, ... ) COUNT #define VA_SIZE( ... ) GET_COUNT( __VA_ARGS__, 6, 5, 4, 3, 2, 1 ) 

Código de la biblioteca:

 #define CAT( A, B ) A ## B #define SELECT( NAME, NUM ) CAT( NAME ## _, NUM ) #define GET_COUNT( _1, _2, _3, _4, _5, _6 /* ad nauseam */, COUNT, ... ) COUNT #define VA_SIZE( ... ) GET_COUNT( __VA_ARGS__, 6, 5, 4, 3, 2, 1 ) #define VA_SELECT( NAME, ... ) SELECT( NAME, VA_SIZE(__VA_ARGS__) )(__VA_ARGS__) 

Uso:

 #define MY_OVERLOADED( ... ) VA_SELECT( MY_OVERLOADED, __VA_ARGS__ ) #define MY_OVERLOADED_1( X ) foo< X > #define MY_OVERLOADED_2( X, Y ) bar< X >( Y ) #define MY_OVERLOADED_3( X, Y, Z ) bang_ ## X< Y >.Z() 

Lo publicaría como un comentario en la publicación de Potatoswatter, pero es demasiado largo y requiere una lista de códigos.

Aquí hay un poco de código perl para generar un conjunto de macros que están destinadas a ser macros sobrecargadas.

 $ perl -le 'map{ $arity = $_; map { $ar = 2 + $arity + $_; $arm = $ar - 1; $arlist = join("", map{"A$_, "} 1..$arity); $warlist = "WHAT, $arlist"; @li = map {"_$_"} 0..$_; $lis = join(", ", @li); $lim = pop @li; $lims = join(", ", @li); print "#define FEI_${arity}A_$ar($warlist$lis) FEI_${arity}A_$arm($warlist$lims) WHAT($_, $arlist$lim)" } 1..3; print "" } 0..4' 

Aquí está el resultado del script:

 #define FEI_0A_3(WHAT, _0, _1) FEI_0A_2(WHAT, _0) WHAT(1, _1) #define FEI_0A_4(WHAT, _0, _1, _2) FEI_0A_3(WHAT, _0, _1) WHAT(2, _2) #define FEI_0A_5(WHAT, _0, _1, _2, _3) FEI_0A_4(WHAT, _0, _1, _2) WHAT(3, _3) #define FEI_1A_4(WHAT, A1, _0, _1) FEI_1A_3(WHAT, A1, _0) WHAT(1, A1, _1) #define FEI_1A_5(WHAT, A1, _0, _1, _2) FEI_1A_4(WHAT, A1, _0, _1) WHAT(2, A1, _2) #define FEI_1A_6(WHAT, A1, _0, _1, _2, _3) FEI_1A_5(WHAT, A1, _0, _1, _2) WHAT(3, A1, _3) #define FEI_2A_5(WHAT, A1, A2, _0, _1) FEI_2A_4(WHAT, A1, A2, _0) WHAT(1, A1, A2, _1) #define FEI_2A_6(WHAT, A1, A2, _0, _1, _2) FEI_2A_5(WHAT, A1, A2, _0, _1) WHAT(2, A1, A2, _2) #define FEI_2A_7(WHAT, A1, A2, _0, _1, _2, _3) FEI_2A_6(WHAT, A1, A2, _0, _1, _2) WHAT(3, A1, A2, _3) #define FEI_3A_6(WHAT, A1, A2, A3, _0, _1) FEI_3A_5(WHAT, A1, A2, A3, _0) WHAT(1, A1, A2, A3, _1) #define FEI_3A_7(WHAT, A1, A2, A3, _0, _1, _2) FEI_3A_6(WHAT, A1, A2, A3, _0, _1) WHAT(2, A1, A2, A3, _2) #define FEI_3A_8(WHAT, A1, A2, A3, _0, _1, _2, _3) FEI_3A_7(WHAT, A1, A2, A3, _0, _1, _2) WHAT(3, A1, A2, A3, _3) #define FEI_4A_7(WHAT, A1, A2, A3, A4, _0, _1) FEI_4A_6(WHAT, A1, A2, A3, A4, _0) WHAT(1, A1, A2, A3, A4, _1) #define FEI_4A_8(WHAT, A1, A2, A3, A4, _0, _1, _2) FEI_4A_7(WHAT, A1, A2, A3, A4, _0, _1) WHAT(2, A1, A2, A3, A4, _2) #define FEI_4A_9(WHAT, A1, A2, A3, A4, _0, _1, _2, _3) FEI_4A_8(WHAT, A1, A2, A3, A4, _0, _1, _2) WHAT(3, A1, A2, A3, A4, _3) 

Estas son las (secciones regularmente estructuradas de) grupos de sobrecargas macro que se utilizan para generar FOR_EACH (también conocidas como FE ) que pueden enviar un macro WHAT opcionalmente con un número arbitrario de argumentos constantes ( A1 , A2 …) además de un número arbitrario de argumentos en una lista, junto con un índice en el orden correcto (una implementación ingenua sin usar algo como SELECT para la sobrecarga produciría índices invertidos).

Como ejemplo, la sección restante (la parte de “caso base” no regular del segundo bloque) tiene el siguiente aspecto:

 #define FE_INDEXED_1ARG(...) VA_SELECT(FEI_1A, __VA_ARGS__) #define FEI_1A_3(WHAT, A1, _0) WHAT(0, A1, _0) 

La utilidad de esto puede ponerse en tela de juicio (la construí porque vi su uso …), y tampoco responde esto directamente a la pregunta del OP (de hecho, hace más bien lo contrario: una construcción foreach hace lo mismo con todos los argumentos variados …), pero pensé que la técnica es bastante interesante (y también terriblemente horrible de alguna manera) y permite bastante poder expresivo usando el preprocesador y será posible generar código de máquina muy eficiente de esta manera. Creo que también sirve como un conmovedor ejemplo de por qué personalmente creo que el preprocesador de C todavía tiene margen de mejora.

Con lo que quiero decir que el preprocesador C es una abominación absoluta y probablemente deberíamos descartarlo y empezar de cero 🙂

Lo siguiente es una mejora sobre la respuesta de Potatoswatter , que puede diferenciar entre cero y un argumento.

En pocas palabras, cuando __VA_ARGS__ está vacío, EXPAND __VA_ARGS__ () dentro de la macro VA_SIZE convierte en EXPAND () y se sustituye por 6 comas. Entonces, VA_SIZE... convierte en COMPOSE( GET_COUNT, (,,,,,, , 0, 6, 5, 4, 3, 2, 1) ) , y eso se convierte en GET_COUNT (,,,,,, , 0, 6, 5, 4, 3, 2, 1) y devuelve 0.

Por otro lado, cuando __VA_ARGS__ es, por ejemplo, int, 5 , EXPAND __VA_ARGS__ () convierte en EXPAND int, 5 () . Entonces, VA_SIZE... convierte en COMPOSE( GET_COUNT, (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1) ) , que se convierte en GET_COUNT (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1) y devuelve 2, como se describe en la respuesta de Potatoswatter.

EXPAND idea de EXPANDIR de la respuesta de Jason Dang .

Código de la biblioteca:

 #define CAT( A, B ) A ## B #define SELECT( NAME, NUM ) CAT( NAME ## _, NUM ) #define COMPOSE( NAME, ARGS ) NAME ARGS #define GET_COUNT( _0, _1, _2, _3, _4, _5, _6 /* ad nauseam */, COUNT, ... ) COUNT #define EXPAND() ,,,,,, // 6 commas (or 7 empty tokens) #define VA_SIZE( ... ) COMPOSE( GET_COUNT, (EXPAND __VA_ARGS__ (), 0, 6, 5, 4, 3, 2, 1) ) #define VA_SELECT( NAME, ... ) SELECT( NAME, VA_SIZE(__VA_ARGS__) )(__VA_ARGS__) 

Uso:

 #define MY_OVERLOADED( ... ) VA_SELECT( MY_OVERLOADED, __VA_ARGS__ ) #define MY_OVERLOADED_0( ) meh() #define MY_OVERLOADED_1( X ) foo< X > #define MY_OVERLOADED_2( X, Y ) bar< X >( Y ) #define MY_OVERLOADED_3( X, Y, Z ) bang_ ## X< Y >.Z() MY_OVERLOADED() // meh() MY_OVERLOADED(bool) // foo< bool > MY_OVERLOADED(int, 5) // bar< int >( 5 ) MY_OVERLOADED(me, double, now) // bang_me< double >.now() 

Aunque ya está respondida, he preparado una versión muy corta de la misma. Espero que pueda ayudar.

Implementación

 // Variable Argument Macro (VA_MACRO) upto 6 arguments #define NUM_ARGS_(_1, _2, _3, _4, _5, _6, TOTAL, ...) TOTAL #define NUM_ARGS(...) NUM_ARGS_(__VA_ARGS__, 6, 5, 4, 3, 2, 1) #define CONCATE_(X, Y) X##Y // Fixed the double '_' from previous code #define CONCATE(MACRO, NUMBER) CONCATE_(MACRO, NUMBER) #define VA_MACRO(MACRO, ...) CONCATE(MACRO, NUM_ARGS(__VA_ARGS__))(__VA_ARGS__) 

Personalización

 // This is how user may define own set of variadic macros #define MY_MACRO(...) VA_MACRO(MY_MACRO, __VA_ARGS__) #define MY_MACRO1(_1) "One" #define MY_MACRO2(_1, _2) "Two" #define MY_MACRO3(_1, _2, _3) "Three" 

Uso

 // While using those, user needs to use only the main macro int main () { auto one = MY_MACRO(1); auto two = MY_MACRO(1, 2); auto three = MY_MACRO(1, 2, 3); }