การใช้มาโครมากเกินไปในจำนวนอาร์กิวเมนต์


183

ฉันมีมาโครสองตัวFOO2และFOO3:

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

ฉันต้องการกำหนดแมโครใหม่FOOดังนี้:

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

แต่วิธีนี้ใช้ไม่ได้เนื่องจากมาโครไม่ทำงานเกินจำนวนอาร์กิวเมนต์

หากไม่มีการแก้ไขFOO2และFOO3มีวิธีกำหนดแมโครFOO(ใช้__VA_ARGS__หรือไม่เช่นนั้น) เพื่อให้ได้ผลการเยี่ยงเดียวFOO(x,y)กับFOO2และFOO(x,y,z)เพื่อFOO3?


1
ฉันมีความรู้สึกที่ดีมากที่ว่านี้ได้รับการถามหลายครั้งก่อน ... [อัปเดต] เช่นที่นี่
Kerrek SB

1
@ KerrekSB: นั่นอาจจะเกี่ยวข้องกันแน่นอนว่ามันต้องไม่ใช่ล่อลวง
Andrew Tomazos

ไม่อาจไม่ใช่อันนั้น แต่มีบางอย่างเช่นนี้เกิดขึ้นเดือนละครั้ง ...
Kerrek SB

เหมือนกันสำหรับ C ++: stackoverflow.com/questions/3046889/ ......ควรเหมือนกันเนื่องจากตัวประมวลผลล่วงหน้าจะเหมือนกันโดยทั่วไป: stackoverflow.com/questions/5085533/…
Ciro Santilli 郝海东冠状病病六四法轮功

ที่เกี่ยวข้อง: stackoverflow.com/questions/11317474/…
Gabriel Staples

คำตอบ:


264

ง่ายเหมือน:

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

ดังนั้นหากคุณมีมาโครเหล่านี้:

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

หากคุณต้องการหนึ่งในสี่:

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

ธรรมชาติถ้าคุณกำหนดFOO2, FOO3และFOO4การส่งออกจะถูกแทนที่โดยผู้มาโครที่กำหนดไว้


5
@ Uroc327 การเพิ่มมาโคร 0 อาร์กิวเมนต์ในรายการเป็นไปได้ดูคำตอบของฉัน
สิงหาคม

7
ไม่ทำงานบน Microsoft Visual Studio 2010 ดูเหมือนว่าVA_ARGSจะถูกขยายเป็นอาร์กิวเมนต์แมโครเดียว
Étienne

9
พบคำตอบนี้เพื่อให้ทำงานภายใต้ MSVC 2010
Étienne

8
ถ้าใครสับสนเป็นวิธีการใช้EXPANDที่กล่าวถึงในการเชื่อมโยง @ Étienneของคุณโดยทั่วไปเรียกมันบนGET_MACROเพื่อต้องการ#define FOO(...) EXPAND(GET_MACRO(__VA_ARGS__, FOO3, FOO2, FOO1)(__VA_ARGS__))และมันควรจะขยายไปยังหมายเลขขวาของการขัดแย้งใน msvc
vexe

3
โปรดทราบว่าใน C ++ 11 ISO C++11 requires at least one argument for the "..." in a variadic macroคุณจะได้รับการแจ้งเตือน: ในการแก้ไขปัญหานี้ให้เพิ่มอาร์กิวเมนต์ที่ไม่ได้ใช้ (หรือแม้แต่แค่เครื่องหมายจุลภาค) หลังพารามิเตอร์สุดท้ายในคำจำกัดความของ FOO (... ): #define FOO(...) GET_MACRO(__VA_ARGS__, FOO3, FOO2, UNUSED)(__VA_ARGS__)( ดูมันทำงานบน Coliru )
โลหะ

49

ในการเพิ่มคำตอบของ netcoderคุณสามารถทำได้ด้วยแมโคร 0 อาร์กิวเมนต์ด้วยความช่วยเหลือของ##__VA_ARGS__ส่วนขยายGCC :

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

1
มันเป็นไปได้ที่จะอนุญาตให้ foo1 และ Foo2 แต่ไม่ FOO0 โดยไม่ต้องทำ#define FOO0 _Pragma("error FOO0 not allowed")?
noɥʇʎԀʎzɐɹƆ

FOO0ไม่ทำงานใน qt + mingw32 การโทรFOO0จะเรียกFOO1
JustWe

มีแนวโน้มมากและเรียบง่าย แต่ไม่ได้ผลกับ FOO0 ด้วย -std = c ++ 11 ... :-(
leonp

1
ปัญหาเดียวกันถ้าคุณกำลังทำเช่นนี้ใน C และคุณพยายามที่จะใช้หรือ-std=c99 -std=c11คุณจำเป็นต้องใช้-std=gnu99หรือ-std=gnu11แทน
Michael Mrozek

1
ปรากฏว่าการแทนที่_0, ##__VA_ARGS__ด้วย_0 __VA_OPT__(,) __VA_ARGS__เป็นวิธีใหม่ในการทำเช่นนี้
Wrzlprmft

36

นี่เป็นวิธีแก้ปัญหาทั่วไปเพิ่มเติม:

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

กำหนดฟังก์ชั่นของคุณ:

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

ตอนนี้คุณสามารถใช้FOOกับอาร์กิวเมนต์ 2, 3 และ 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

ข้อ จำกัด

  • อาร์กิวเมนต์สูงสุด 63 ข้อ (แต่ขยายได้)
  • ฟังก์ชั่นสำหรับไม่มีข้อโต้แย้งใน GCC เท่านั้นที่เป็นไปได้

ไอเดีย

ใช้สำหรับอาร์กิวเมนต์เริ่มต้น:

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

ใช้สำหรับฟังก์ชั่นที่มีจำนวนอาร์กิวเมนต์เป็นไปได้:

#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__คัดลอกมาจาก Laurent Deniau & Roland Illig ที่นี่: https://groups.google.com/group/comp.std.c/browse_thread/thread/77ee8c8f92e4a3fb/346fc464319b1ee5?pli=1


ที่เกี่ยวข้อง: stackoverflow.com/questions/11317474/…
Gabriel Staples

อันนี้ก็เช่นกัน: stackoverflow.com/questions/2124339/…
Gabriel Staples

มาโคร__NARG_I_ดูเหมือนไม่จำเป็นอย่างสมบูรณ์และไม่จำเป็น มันเพิ่มขั้นตอนพิเศษและความสับสน ผมขอแนะนำให้ลบมันทั้งหมดและเพียงแค่กำหนดแทนที่จะเป็น:__NARG__ #define __NARG__(...) __ARG_N(__VA_ARGS__,__RSEQ_N())
Gabriel Staples

หรือว่าจะทำลายการประมวลผลล่วงหน้าหรือไม่ ฉันพลาดอะไรไปรึเปล่า?
Gabriel Staples

เช่นเดียวกับ_VFUNC_: เพียงลบ จากนั้นกำหนด_VFUNCเป็น: แทน#define _VFUNC(name, n) name##n #define _VFUNC(name, n) _VFUNC_(name, n)
Gabriel Staples

15

ฉันเป็นเพียงการวิจัยนี้ตัวเองและฉันมาข้ามนี้ที่นี่ ผู้เขียนเพิ่มการสนับสนุนการโต้แย้งเริ่มต้นสำหรับฟังก์ชั่น C ผ่านทางแมโคร

ฉันจะพยายามสรุปบทความสั้น ๆ โดยทั่วไปคุณต้องกำหนดแมโครที่สามารถนับอาร์กิวเมนต์ แมโครนี้จะส่งคืน 2, 1, 0 หรือช่วงของอาร์กิวเมนต์ที่สามารถรองรับได้ เช่น:

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

ด้วยวิธีนี้คุณจะต้องสร้างมาโครอีกตัวที่รับจำนวนตัวแปรที่มีข้อโต้แย้งนับจำนวนข้อโต้แย้งและเรียกมาโครที่เหมาะสม ฉันได้นำมาโครตัวอย่างของคุณและรวมเข้ากับตัวอย่างของบทความแล้ว ฉันมีฟังก์ชั่นการโทร FOO1 a () และฟังก์ชั่นการโทร FOO2 ด้วยอาร์กิวเมนต์ b (เห็นได้ชัดว่าฉันสมมุติว่า C ++ ที่นี่ แต่คุณสามารถเปลี่ยนมาโครเป็นอะไรก็ได้)

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

ดังนั้นหากคุณมี

FOO(a)
FOO(a,b)

ตัวประมวลผลล่วงหน้าขยายตัวเป็น

a();
a(b);

ฉันจะอ่านบทความที่ฉันเชื่อมโยงอย่างแน่นอน มันมีข้อมูลมากและเขากล่าวว่า NARG2 จะไม่ทำงานบนอาร์กิวเมนต์ที่ว่างเปล่า เขาเดินตามนี้ขึ้นที่นี่


7

นี่คือคำตอบที่กระชับกว่าข้างบนนี้ ด้วยตัวอย่าง

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

วิ่ง:

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

โปรดทราบว่าการมีทั้งสองอย่าง_OVRและ_OVR_EXPANDอาจดูเหมือนซ้ำซ้อน แต่จำเป็นสำหรับผู้ประมวลผลก่อนที่จะขยาย_COUNT_ARGS(__VA_ARGS__)ส่วนซึ่งมิฉะนั้นจะถือว่าเป็นสตริง


ฉันชอบวิธีนี้ สามารถแก้ไขเพื่อจัดการกับแมโครที่มีการโหลดมากเกินไปซึ่งไม่มีข้อโต้แย้งหรือไม่?
แอนดรู

3

บางทีคุณอาจจะสามารถใช้แมโครนี้จะนับจำนวนของการขัดแย้ง

#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

2

นี่คือสปินออกจากคำตอบของ Evgeni Sergeev อันนี้รองรับการโอเวอร์คล็อกแบบ zero zeroเช่นกัน!

ฉันทดสอบสิ่งนี้กับ GCC และ MinGW ควรทำงานกับ C ++ เวอร์ชันเก่าและใหม่ โปรดทราบว่าฉันจะไม่รับประกันสำหรับ MSVC ... แต่ด้วยการปรับแต่งบางอย่างฉันมั่นใจว่ามันสามารถทำงานกับมันได้เช่นกัน

ฉันยังจัดรูปแบบนี้เพื่อวางลงในไฟล์ส่วนหัว (ซึ่งฉันเรียกว่า macroutil.h) หากคุณทำเช่นนั้นคุณสามารถรวมส่วนหัวนี้ได้ทุกสิ่งที่คุณต้องการคุณสมบัติและไม่ดูความไม่เหมาะสมที่เกี่ยวข้องในการใช้งาน

#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

ดูเหมือนว่าจะทำงานได้ดีกับ GCC, Clang และ MSVC นี่เป็นคำตอบบางส่วนที่ได้รับการทำความสะอาดที่นี่

#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 วิธีการปรับแมโครนี้เพื่อที่จะทำงานร่วมกับศูนย์การโต้แย้ง#define func0() foo? รุ่นปัจจุบันไม่ได้จัดการกับกรณีนี้โชคไม่ดี
Jerry Ma
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.