Overloading Makro pada Jumlah Argumen

184

Saya punya dua makro FOO2dan FOO3:

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

Saya ingin mendefinisikan makro baru FOOsebagai berikut:

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

Tetapi ini tidak berhasil karena makro tidak membebani jumlah argumen.

Tanpa memodifikasi FOO2dan FOO3, ada beberapa cara untuk mendefinisikan makro FOO(menggunakan __VA_ARGS__atau sebaliknya) untuk mendapatkan efek yang sama dari pengiriman FOO(x,y)untuk FOO2, dan FOO(x,y,z)untuk FOO3?

Andrew Tomazos
sumber
1
Saya mempunyai perasaan yang sangat kuat bahwa ini telah ditanyakan beberapa kali sebelumnya ... [pembaruan] misalnya di sini .
Kerrek SB
1
@ GerrekSB: Itu mungkin terkait, pasti itu pasti bukan penipuan.
Andrew Tomazos
Tidak, mungkin bukan yang itu, tapi sesuatu seperti ini muncul sebulan sekali ...
Kerrek SB
Sama untuk C ++: stackoverflow.com/questions/3046889/... Harus sama karena preprosesor pada dasarnya sama: stackoverflow.com/questions/5085533/…
Ciro Santilli 郝海东 冠状 病 六四 事件 事件 法轮功
Terkait: stackoverflow.com/questions/11317474/...
Gabriel Staples

Jawaban:

265

Sederhana seperti:

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

Jadi, jika Anda memiliki makro ini:

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

Jika Anda ingin yang keempat:

#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)

Tentu, jika Anda mendefinisikan FOO2, FOO3dan FOO4, output akan digantikan oleh makro yang ditentukan.

netcoder
sumber
5
@ Uroc327 Menambahkan makro 0-argumen ke daftar dimungkinkan, lihat jawaban saya.
augurar
7
Tidak berfungsi di Microsoft Visual Studio 2010, VA_ARGS tampaknya diperluas menjadi argumen makro tunggal.
Étienne
9
Temukan jawaban ini untuk membuatnya bekerja di bawah MSVC 2010.
Étienne
8
Jika ada yang bingung bagaimana cara menggunakan yang EXPANDdisebutkan di tautan @ Étienne, Anda pada dasarnya memintanya GET_MACROseperti itu #define FOO(...) EXPAND(GET_MACRO(__VA_ARGS__, FOO3, FOO2, FOO1)(__VA_ARGS__))dan itu harus diperluas ke jumlah argumen yang tepat di msvc.
vexe
3
Perhatikan bahwa pada C ++ 11, Anda akan mendapatkan peringatan: ISO C++11 requires at least one argument for the "..." in a variadic macro. Untuk memperbaikinya, tambahkan argumen yang tidak digunakan (atau bahkan hanya koma) setelah param terakhir dalam definisi FOO (...): #define FOO(...) GET_MACRO(__VA_ARGS__, FOO3, FOO2, UNUSED)(__VA_ARGS__)( Lihat dijalankan di Coliru ).
logam
49

Untuk menambahkan jawaban netcoder , Anda sebenarnya BISA melakukan ini dengan makro 0-argumen, dengan bantuan ##__VA_ARGS__ekstensi GCC :

#define GET_MACRO(_0, _1, _2, NAME, ...) NAME
#define FOO(...) GET_MACRO(_0, ##__VA_ARGS__, FOO2, FOO1, FOO0)(__VA_ARGS__)
augurar
sumber
1
apakah mungkin untuk mengizinkan FOO1 dan FOO2 tetapi tidak FOO0 tanpa melakukan #define FOO0 _Pragma("error FOO0 not allowed")?
noɥʇʎԀʎzɐɹƆ
FOO0tidak bekerja di qt + mingw32, panggilan FOO0akan memanggilFOO1
JustWe
Sangat menjanjikan dan sederhana. Tetapi tidak bekerja untuk FOO0 dengan -std = c ++ 11 ... :-(
leonp
1
Masalah yang sama jika Anda melakukan ini di C dan Anda mencoba menggunakan -std=c99atau -std=c11. Anda perlu menggunakan -std=gnu99atau -std=gnu11sebagai gantinya
Michael Mrozek
1
Tampaknya mengganti _0, ##__VA_ARGS__dengan _0 __VA_OPT__(,) __VA_ARGS__adalah cara baru untuk melakukan ini.
Wrzlprmft
36

Ini solusi yang lebih umum:

// 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__)

Tentukan fungsi Anda:

#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; }

Sekarang Anda dapat menggunakan FOOdengan argumen 2, 3 dan 4:

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

Keterbatasan

  • Hanya hingga 63 argumen (tetapi dapat diperluas)
  • Berfungsi tanpa argumen hanya di GCC

Ide ide

Gunakan untuk argumen default:

#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) { /* ... */ }

Gunakan untuk fungsi dengan kemungkinan jumlah argumen tak terbatas:

#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__disalin dari Laurent Deniau & Roland Illig di sini: https://groups.google.com/group/comp.std.c/browse_thread/thread/77ee8c8f92e4a3fb/346fc464319b1ee5?pli=1

R1tschY
sumber
Terkait: stackoverflow.com/questions/11317474/...
Gabriel Staples
Yang ini juga: stackoverflow.com/questions/2124339/…
Gabriel Staples
Makro __NARG_I_tampaknya sama sekali tidak perlu dan berlebihan. Itu hanya menambah langkah ekstra, dan kebingungan. Saya merekomendasikan menghapus itu sepenuhnya dan hanya mendefinisikan __NARG__sebagai gantinya sebagai: #define __NARG__(...) __ARG_N(__VA_ARGS__,__RSEQ_N()).
Gabriel Staples
Atau akankah hal itu merusak pra-pemrosesan? Apakah saya melewatkan sesuatu?
Gabriel Staples
Sama dengan _VFUNC_: hapus saja. Kemudian, definisikan _VFUNCsebagai: #define _VFUNC(name, n) name##nbukan #define _VFUNC(name, n) _VFUNC_(name, n).
Gabriel Staples
15

Saya sendiri sedang meneliti ini, dan saya menemukan ini di sini . Penulis menambahkan dukungan argumen default untuk fungsi C melalui makro.

Saya akan mencoba meringkas artikel secara singkat. Pada dasarnya, Anda perlu mendefinisikan makro yang dapat menghitung argumen. Makro ini akan menghasilkan 2, 1, 0, atau rentang argumen apa pun yang dapat didukungnya. Misalnya:

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

Dengan ini, Anda perlu membuat makro lain yang mengambil sejumlah variabel argumen, menghitung argumen, dan memanggil makro yang sesuai. Saya telah mengambil contoh makro Anda dan menggabungkannya dengan contoh artikel. Saya memiliki fungsi panggilan FOO1 a () dan fungsi panggilan FOO2 dengan argumen b (jelas, saya mengasumsikan C ++ di sini, tetapi Anda dapat mengubah makro untuk apa pun).

#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__)

Jadi kalau sudah

FOO(a)
FOO(a,b)

Preprocessor memperluas itu ke

a();
a(b);

Saya pasti akan membaca artikel yang saya tautkan. Ini sangat informatif dan ia menyebutkan bahwa NARG2 tidak akan bekerja pada argumen kosong. Dia mengikuti ini di sini .

lamunong
sumber
7

Ini adalah versi yang lebih ringkas dari jawaban di atas . Dengan contoh.

#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;
}

Lari:

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

Perhatikan bahwa memiliki keduanya _OVRdan _OVR_EXPANDmungkin terlihat berlebihan, tetapi perlu bagi preprosesor untuk memperluas _COUNT_ARGS(__VA_ARGS__)bagian, yang jika tidak diperlakukan sebagai string.

Evgeni Sergeev
sumber
Saya suka solusi ini. Bisakah itu dimodifikasi untuk menangani makro kelebihan beban yang tidak membutuhkan argumen?
Andrew
3

Mungkin Anda bisa menggunakan makro ini untuk menghitung jumlah argumen .

#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1)
#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,...) N
eyalm
sumber
2

Ini adalah spin off dari jawaban Evgeni Sergeev. Yang ini mendukung kelebihan argumen nol !

Saya menguji ini dengan GCC dan MinGW. Seharusnya bekerja dengan versi lama dan baru dari C ++. Perhatikan bahwa saya tidak akan memberikan jaminan untuk MSVC ... Tetapi dengan beberapa penyesuaian, saya yakin itu dapat dibuat untuk bekerja dengan itu juga.

Saya juga memformat ini untuk ditempelkan ke file header (yang saya sebut macroutil.h). Jika Anda melakukannya, Anda bisa memasukkan header ini apa pun yang Anda butuhkan fitur, dan tidak melihat nastiness yang terlibat dalam implementasi.

#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
Terima kasih
sumber
2

Ini tampaknya berfungsi dengan baik pada GCC, Dentang dan MSVC. Ini adalah versi yang sudah dibersihkan dari beberapa jawaban di sini

#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__)
Rian Quinn
sumber
1
@RianQuinn Bagaimana cara menyesuaikan makro ini sehingga berfungsi dengan argumen nol #define func0() foo? Sayangnya, versi saat ini tidak menangani kasus ini.
Jerry Ma