Sovraccarico di macro sul numero di argomenti


184

Ho due macro FOO2e FOO3:

#define FOO2(x,y) ...
#define FOO3(x,y,z) ...

Voglio definire una nuova macro FOOcome segue:

#define FOO(x,y) FOO2(x,y)
#define FOO(x,y,z) FOO3(x,y,z)

Ma questo non funziona perché le macro non sovraccaricano il numero di argomenti.

Senza modificare FOO2e FOO3, c'è un modo per definire una macro FOO(usando __VA_ARGS__o in altro modo) per ottenere lo stesso effetto del dispacciamento FOO(x,y)verso FOO2e FOO(x,y,z)verso FOO3?


1
Ho la sensazione molto forte che questo sia stato chiesto più volte prima ... [aggiornamento] ad esempio qui .
Kerrek SB,

1
@KerrekSB: Questo può essere correlato, se non è certamente un duplicato.
Andrew Tomazos,

No, forse non quello, ma qualcosa del genere si presenta una volta al mese ...
Kerrek SB

Lo stesso vale per C ++: stackoverflow.com/questions/3046889/... dovrebbe essere lo stesso dal momento che i preprocessori sono fondamentalmente gli stessi: stackoverflow.com/questions/5085533/...
Ciro Santilli郝海东冠状病六四事件法轮功

Risposte:


265

Semplice come:

#define GET_MACRO(_1,_2,_3,NAME,...) NAME
#define FOO(...) GET_MACRO(__VA_ARGS__, FOO3, FOO2)(__VA_ARGS__)

Quindi se hai queste macro:

FOO(World, !)         # expands to FOO2(World, !)
FOO(foo,bar,baz)      # expands to FOO3(foo,bar,baz)

Se vuoi un quarto:

#define GET_MACRO(_1,_2,_3,_4,NAME,...) NAME
#define FOO(...) GET_MACRO(__VA_ARGS__, FOO4, FOO3, FOO2)(__VA_ARGS__)

FOO(a,b,c,d)          # expeands to FOO4(a,b,c,d)

Naturalmente, se definito FOO2, FOO3e FOO4, l'output verrà sostituito da quelli delle macro definite.


5
@ Uroc327 È possibile aggiungere una macro di 0 argomenti all'elenco, vedere la mia risposta.
agosto

7
Non funziona su Microsoft Visual Studio 2010, VA_ARGS sembra essere espanso in un singolo argomento macro.
Étienne,

9
Ho trovato questa risposta per farlo funzionare sotto MSVC 2010.
Étienne,

8
Se qualcuno è confuso su come usare il EXPANDmenzionato nel link di @ Étienne, in pratica lo invochi in questo GET_MACROmodo #define FOO(...) EXPAND(GET_MACRO(__VA_ARGS__, FOO3, FOO2, FOO1)(__VA_ARGS__))e dovrebbe espandersi al giusto numero di argomenti in msvc.
vexe,

3
Si noti che il C ++ 11, si otterrà un avvertimento: ISO C++11 requires at least one argument for the "..." in a variadic macro. Per risolvere questo problema, aggiungi un argomento inutilizzato (o anche solo una virgola) dopo l'ultimo parametro nella definizione di FOO (...): #define FOO(...) GET_MACRO(__VA_ARGS__, FOO3, FOO2, UNUSED)(__VA_ARGS__)( Vedi eseguilo su Coliru ).
metallo,

49

Per aggiungere alla risposta di netcoder , PUOI infatti farlo con una macro a 0 argomenti, con l'aiuto ##__VA_ARGS__dell'estensione GCC :

#define GET_MACRO(_0, _1, _2, NAME, ...) NAME
#define FOO(...) GET_MACRO(_0, ##__VA_ARGS__, FOO2, FOO1, FOO0)(__VA_ARGS__)

1
è possibile consentire FOO1 e FOO2 ma non FOO0 senza farlo #define FOO0 _Pragma("error FOO0 not allowed")?
noɥʇʎԀʎzɐɹƆ

FOO0non funziona in qt + mingw32, la chiamata FOO0invocherà ilFOO1
JustWe

Molto promettente e semplice. Ma non funziona per FOO0 con -std = c ++ 11 ... :-(
leonp

1
Stesso problema se lo stai facendo in C e provi a usare -std=c99o -std=c11. Devi usare -std=gnu99o -std=gnu11invece
Michael Mrozek l'

1
Sembra che sostituirlo _0, ##__VA_ARGS__con _0 __VA_OPT__(,) __VA_ARGS__sia il nuovo modo per farlo.
Wrzlprmft

36

Ecco una soluzione più generale:

// get number of arguments with __NARG__
#define __NARG__(...)  __NARG_I_(__VA_ARGS__,__RSEQ_N())
#define __NARG_I_(...) __ARG_N(__VA_ARGS__)
#define __ARG_N( \
      _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
     _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
     _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
     _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
     _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
     _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
     _61,_62,_63,N,...) N
#define __RSEQ_N() \
     63,62,61,60,                   \
     59,58,57,56,55,54,53,52,51,50, \
     49,48,47,46,45,44,43,42,41,40, \
     39,38,37,36,35,34,33,32,31,30, \
     29,28,27,26,25,24,23,22,21,20, \
     19,18,17,16,15,14,13,12,11,10, \
     9,8,7,6,5,4,3,2,1,0

// general definition for any function name
#define _VFUNC_(name, n) name##n
#define _VFUNC(name, n) _VFUNC_(name, n)
#define VFUNC(func, ...) _VFUNC(func, __NARG__(__VA_ARGS__)) (__VA_ARGS__)

// definition for FOO
#define FOO(...) VFUNC(FOO, __VA_ARGS__)

Definisci le tue funzioni:

#define FOO2(x, y) ((x) + (y))
#define FOO3(x, y, z) ((x) + (y) + (z))

// it also works with C functions:
int FOO4(int a, int b, int c, int d) { return a + b + c + d; }

Ora puoi usare FOOcon 2, 3 e 4 argomenti:

FOO(42, 42) // will use makro function FOO2
FOO(42, 42, 42) // will use makro function FOO3
FOO(42, 42, 42, 42) // will call FOO4 function

limitazioni

  • Solo fino a 63 argomenti (ma espandibile)
  • Funzione per nessun argomento possibile solo in GCC

idee

Usalo per argomenti predefiniti:

#define func(...) VFUNC(func, __VA_ARGS__)
#define func2(a, b) func4(a, b, NULL, NULL)
#define func3(a, b, c) func4(a, b, c, NULL)

// real function:
int func4(int a, int b, void* c, void* d) { /* ... */ }

Usalo per funzioni con un numero infinito possibile di argomenti:

#define SUM(...) VFUNC(SUM, __VA_ARGS__)
#define SUM2(a, b) ((a) + (b))
#define SUM3(a, b, c) ((a) + (b) + (c))
#define SUM4(a, b, c) ((a) + (b) + (c) + (d))
// ...

PS: __NARG__è copiato da Laurent Deniau e Roland Illig qui: https://groups.google.com/group/comp.std.c/browse_thread/thread/77ee8c8f92e4a3fb/346fc464319b1ee5?pli=1




La macro __NARG_I_sembra completamente superflua e superflua. Aggiunge solo un ulteriore passaggio e confusione. Vi consiglio di eliminarlo del tutto e solo definendo __NARG__invece come: #define __NARG__(...) __ARG_N(__VA_ARGS__,__RSEQ_N()).
Gabriel Staples,

O questo in qualche modo interromperà la pre-elaborazione? Mi sto perdendo qualcosa?
Gabriel Staples,

Lo stesso con _VFUNC_: basta cancellarlo. Quindi, definire _VFUNCcome: #define _VFUNC(name, n) name##nanziché #define _VFUNC(name, n) _VFUNC_(name, n).
Gabriel Staples,

15

Lo stavo solo studiando da solo, e mi sono imbattuto in questo qui . L'autore ha aggiunto il supporto argomenti predefinito per le funzioni C tramite macro.

Proverò a riassumere brevemente l'articolo. Fondamentalmente, è necessario definire una macro che può contare gli argomenti. Questa macro restituirà 2, 1, 0 o qualsiasi intervallo di argomenti che può supportare. Per esempio:

#define _ARG2(_0, _1, _2, ...) _2
#define NARG2(...) _ARG2(__VA_ARGS__, 2, 1, 0)

Con questo, è necessario creare un'altra macro che accetta un numero variabile di argomenti, conta gli argomenti e chiama la macro appropriata. Ho preso la tua macro di esempio e l'ho combinata con l'esempio dell'articolo. Ho la funzione di chiamata FOO1 a () e la funzione di chiamata FOO2 a con argomento b (ovviamente, sto assumendo C ++ qui, ma puoi cambiare la macro in qualunque cosa).

#define FOO1(a) a();
#define FOO2(a,b) a(b);

#define _ARG2(_0, _1, _2, ...) _2
#define NARG2(...) _ARG2(__VA_ARGS__, 2, 1, 0)

#define _ONE_OR_TWO_ARGS_1(a) FOO1(a)
#define _ONE_OR_TWO_ARGS_2(a, b) FOO2(a,b)

#define __ONE_OR_TWO_ARGS(N, ...) _ONE_OR_TWO_ARGS_ ## N (__VA_ARGS__)
#define _ONE_OR_TWO_ARGS(N, ...) __ONE_OR_TWO_ARGS(N, __VA_ARGS__)

#define FOO(...) _ONE_OR_TWO_ARGS(NARG2(__VA_ARGS__), __VA_ARGS__)

Quindi se hai

FOO(a)
FOO(a,b)

Il preprocessore lo espande a

a();
a(b);

Leggerei sicuramente l'articolo che ho collegato. È molto istruttivo e afferma che NARG2 non funzionerà su argomenti vuoti. Lo segue qui .


7

Ecco una versione più compatta della risposta sopra . Con esempio.

#include <iostream>
using namespace std;

#define OVERLOADED_MACRO(M, ...) _OVR(M, _COUNT_ARGS(__VA_ARGS__)) (__VA_ARGS__)
#define _OVR(macroName, number_of_args)   _OVR_EXPAND(macroName, number_of_args)
#define _OVR_EXPAND(macroName, number_of_args)    macroName##number_of_args

#define _COUNT_ARGS(...)  _ARG_PATTERN_MATCH(__VA_ARGS__, 9,8,7,6,5,4,3,2,1)
#define _ARG_PATTERN_MATCH(_1,_2,_3,_4,_5,_6,_7,_8,_9, N, ...)   N


//Example:
#define ff(...)     OVERLOADED_MACRO(ff, __VA_ARGS__)
#define ii(...)     OVERLOADED_MACRO(ii, __VA_ARGS__)

#define ff3(c, a, b) for (int c = int(a); c < int(b); ++c)
#define ff2(c, b)   ff3(c, 0, b)

#define ii2(a, b)   ff3(i, a, b)
#define ii1(n)      ii2(0, n)


int main() {
    ff (counter, 3, 5)
        cout << "counter = " << counter << endl;
    ff (abc, 4)
        cout << "abc = " << abc << endl;
    ii (3)
        cout << "i = " << i << endl;
    ii (100, 103)
        cout << "i = " << i << endl;


    return 0;
}

Correre:

User@Table 13:06:16 /c/T
$ g++ test_overloaded_macros.cpp 

User@Table 13:16:26 /c/T
$ ./a.exe
counter = 3
counter = 4
abc = 0
abc = 1
abc = 2
abc = 3
i = 0
i = 1
i = 2
i = 100
i = 101
i = 102

Si noti che avere entrambi _OVRe _OVR_EXPANDpuò sembrare ridondante, ma è necessario che il preprocessore espanda la _COUNT_ARGS(__VA_ARGS__)parte, che altrimenti viene trattata come una stringa.


Mi piace questa soluzione. Può essere modificato per gestire una macro sovraccaricata che accetta zero argomenti?
Andrew,


2

Ecco uno spin-off della risposta di Evgeni Sergeev. Questo supporta anche sovraccarichi di argomento zero !

Ho provato questo con GCC e MinGW. Dovrebbe funzionare con vecchie e nuove versioni di C ++. Nota che non lo garantirò per MSVC ... Ma con qualche modifica, sono sicuro che potrebbe essere fatto funzionare anche con quello.

Ho anche formattato questo per essere incollato in un file di intestazione (che ho chiamato macroutil.h). Se lo fai, puoi semplicemente includere questa intestazione qualunque sia la funzione necessaria e non guardare la cattiveria implicata nell'implementazione.

#ifndef MACROUTIL_H
#define MACROUTIL_H

//-----------------------------------------------------------------------------
// OVERLOADED_MACRO
//
// used to create other macros with overloaded argument lists
//
// Example Use:
// #define myMacro(...) OVERLOADED_MACRO( myMacro, __VA_ARGS__ )
// #define myMacro0() someFunc()
// #define myMacro1( arg1 ) someFunc( arg1 )
// #define myMacro2( arg1, arg2 ) someFunc( arg1, arg2 )
//
// myMacro();
// myMacro(1);
// myMacro(1,2);
//
// Note the numerical suffix on the macro names,
// which indicates the number of arguments.
// That is the REQUIRED naming convention for your macros.
//
//-----------------------------------------------------------------------------

// OVERLOADED_MACRO
// derived from: /programming/11761703/overloading-macro-on-number-of-arguments
// replaced use of _COUNT_ARGS macro with VA_NUM_ARGS defined below
// to support of zero argument overloads
#define OVERLOADED_MACRO(M, ...) _OVR(M, VA_NUM_ARGS(__VA_ARGS__)) (__VA_ARGS__)
#define _OVR(macroName, number_of_args)   _OVR_EXPAND(macroName, number_of_args)
#define _OVR_EXPAND(macroName, number_of_args)    macroName##number_of_args
//#define _COUNT_ARGS(...)  _ARG_PATTERN_MATCH(__VA_ARGS__, 15,14,13,12,11,10,9,8,7,6,5,4,3,2,1)
#define _ARG_PATTERN_MATCH(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15, N, ...)   N

// VA_NUM_ARGS
// copied from comments section of:
// http://efesx.com/2010/07/17/variadic-macro-to-count-number-of-arguments/
// which itself was derived from:
// https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/
#define _ARG16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
#define HAS_COMMA(...) _ARG16(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
#define HAS_NO_COMMA(...) _ARG16(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)
#define _TRIGGER_PARENTHESIS_(...) ,

#define HAS_ZERO_OR_ONE_ARGS(...) \
    _HAS_ZERO_OR_ONE_ARGS( \
    /* test if there is just one argument, eventually an empty one */ \
    HAS_COMMA(__VA_ARGS__), \
    /* test if _TRIGGER_PARENTHESIS_ together with the argument adds a comma */ \
    HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__), \
    /* test if the argument together with a parenthesis adds a comma */ \
    HAS_COMMA(__VA_ARGS__ (~)), \
    /* test if placing it between _TRIGGER_PARENTHESIS_ and the parenthesis adds a comma */ \
    HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__ (~)) \
    )

#define PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4
#define _HAS_ZERO_OR_ONE_ARGS(_0, _1, _2, _3) HAS_NO_COMMA(PASTE5(_IS_EMPTY_CASE_, _0, _1, _2, _3))
#define _IS_EMPTY_CASE_0001 ,

#define _VA0(...) HAS_ZERO_OR_ONE_ARGS(__VA_ARGS__)
#define _VA1(...) HAS_ZERO_OR_ONE_ARGS(__VA_ARGS__)
#define _VA2(...) 2
#define _VA3(...) 3
#define _VA4(...) 4
#define _VA5(...) 5
#define _VA6(...) 6
#define _VA7(...) 7
#define _VA8(...) 8
#define _VA9(...) 9
#define _VA10(...) 10
#define _VA11(...) 11
#define _VA12(...) 12
#define _VA13(...) 13
#define _VA14(...) 14
#define _VA15(...) 15
#define _VA16(...) 16

#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, PP_RSEQ_N(__VA_ARGS__) )
#define VA_NUM_ARGS_IMPL(...) VA_NUM_ARGS_N(__VA_ARGS__)

#define VA_NUM_ARGS_N( \
    _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
    _11,_12,_13,_14,_15,_16,N,...) N

#define PP_RSEQ_N(...) \
    _VA16(__VA_ARGS__),_VA15(__VA_ARGS__),_VA14(__VA_ARGS__),_VA13(__VA_ARGS__), \
    _VA12(__VA_ARGS__),_VA11(__VA_ARGS__),_VA10(__VA_ARGS__), _VA9(__VA_ARGS__), \
    _VA8(__VA_ARGS__),_VA7(__VA_ARGS__),_VA6(__VA_ARGS__),_VA5(__VA_ARGS__), \
    _VA4(__VA_ARGS__),_VA3(__VA_ARGS__),_VA2(__VA_ARGS__),_VA1(__VA_ARGS__), \
    _VA0(__VA_ARGS__)

//-----------------------------------------------------------------------------

#endif // MACROUTIL_H

2

Questo sembra funzionare bene su GCC, Clang e MSVC. È una versione pulita di alcune delle risposte qui

#define _my_BUGFX(x) x

#define _my_NARG2(...) _my_BUGFX(_my_NARG1(__VA_ARGS__,_my_RSEQN()))
#define _my_NARG1(...) _my_BUGFX(_my_ARGSN(__VA_ARGS__))
#define _my_ARGSN(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,N,...) N
#define _my_RSEQN() 10,9,8,7,6,5,4,3,2,1,0

#define _my_FUNC2(name,n) name ## n
#define _my_FUNC1(name,n) _my_FUNC2(name,n)
#define GET_MACRO(func,...) _my_FUNC1(func,_my_BUGFX(_my_NARG2(__VA_ARGS__))) (__VA_ARGS__)

#define FOO(...) GET_MACRO(FOO,__VA_ARGS__)

1
@RianQuinn Come regolare questa macro in modo che funzioni con argomento zero #define func0() foo? Purtroppo la versione attuale non gestisce questo caso.
Jerry Ma
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.