Quá tải Macro về số lượng đối số


183

Tôi có hai macro FOO2FOO3 :

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

Tôi muốn xác định một macro mới FOOnhư sau:

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

Nhưng điều này không hoạt động vì các macro không quá tải về số lượng đối số.

Mà không sửa đổi FOO2FOO3, có một số cách để xác định một macro FOO(sử dụng __VA_ARGS__hay cách khác) để có được những tác dụng tương tự của cử FOO(x,y)đến FOO2, và FOO(x,y,z)để FOO3?


1
Tôi có một cảm giác rất mạnh mẽ rằng điều này đã được hỏi nhiều lần trước khi ... [cập nhật], vd: ở đây .
Kerrek SB

1
@KerrekSB: Điều đó có thể liên quan, chắc chắn nó không phải là một bản dupe.
Andrew Tomazos

Không, có lẽ không phải cái đó, nhưng một cái gì đó như thế này xuất hiện mỗi tháng một lần ...
Kerrek SB

Tương tự đối với C ++: stackoverflow.com/questions/3046889/ Hãy nên giống nhau vì các bộ tiền xử lý về cơ bản là giống nhau: stackoverflow.com/questions/5085533/
Ciro Santilli 冠状 病 六四 法轮功

Câu trả lời:


262

Đơn giản như:

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

Vì vậy, nếu bạn có các macro này:

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

Nếu bạn muốn một cái thứ tư:

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

Đương nhiên, nếu bạn định nghĩa FOO2, FOO3FOO4, kết quả sẽ được thay thế bởi những macro được xác định.


5
@ Uroc327 Thêm macro đối số 0 vào danh sách là có thể, xem câu trả lời của tôi.
tám

7
Không hoạt động trên Microsoft Visual Studio 2010, VA_ARGS dường như được mở rộng thành một đối số macro duy nhất.
Étienne

9
Tìm thấy câu trả lời này để làm cho nó hoạt động theo MSVC 2010
Étienne

8
Nếu bất kỳ ai nhầm lẫn về cách sử dụng EXPANDliên kết được đề cập trong liên kết của @ Étienne, về cơ bản, bạn sẽ gọi nó GET_MACROnhư vậy #define FOO(...) EXPAND(GET_MACRO(__VA_ARGS__, FOO3, FOO2, FOO1)(__VA_ARGS__))và nó sẽ mở rộng đến đúng số lượng đối số trong msvc.
vexe

3
Lưu ý rằng trên C ++ 11, bạn sẽ nhận được cảnh báo: ISO C++11 requires at least one argument for the "..." in a variadic macro . Để khắc phục điều này, hãy thêm một đối số không được sử dụng (hoặc thậm chí chỉ là dấu phẩy) sau thông số cuối cùng trong định nghĩa của FOO (...): #define FOO(...) GET_MACRO(__VA_ARGS__, FOO3, FOO2, UNUSED)(__VA_ARGS__)( Xem nó chạy trên Coliru ).
kim loại

49

Để thêm vào câu trả lời của netcoder , trên thực tế bạn CÓ THỂ làm điều này với macro đối số 0, với sự trợ giúp của ##__VA_ARGS__tiện ích mở rộng GCC :

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

1
có thể cho phép FOO1 và FOO2 nhưng không cho phép FOO0 mà không làm gì #define FOO0 _Pragma("error FOO0 not allowed")không?
noɥʇʎԀʎzɐɹƆ

FOO0không hoạt động trong qt + mingw32, cuộc gọi FOO0sẽ gọiFOO1
Justwe

Rất hứa hẹn và đơn giản. Nhưng không hoạt động cho FOO0 với -std = c ++ 11 ... :-(
leonp

1
Vấn đề tương tự nếu bạn đang làm điều này trong C và bạn cố gắng sử dụng -std=c99hoặc -std=c11. Bạn cần sử dụng -std=gnu99hoặc -std=gnu11thay vào đó
Michael Mrozek

1
Có vẻ như thay thế _0, ##__VA_ARGS__bằng _0 __VA_OPT__(,) __VA_ARGS__là cách mới để làm điều này.
Wrzlprmft

36

Đây là một giải pháp tổng quát hơn:

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

Xác định chức năng của bạn:

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

Bây giờ bạn có thể sử dụng FOOvới 2, 3 và 4 đối số:

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

Hạn chế

  • Chỉ có tối đa 63 đối số (nhưng có thể mở rộng)
  • Chức năng không có đối số chỉ trong GCC có thể

Ý tưởng

Sử dụng nó cho các đối số mặc định:

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

Sử dụng nó cho các hàm với số lượng đối số vô hạn có thể:

#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__được sao chép từ Laurent Deniau & Roland Illig tại đây: https://groups.google.com/group/comp.std.c/browse_thread/thread/77ee8c8f92e4a3fb/346fc464319b1ee5?pli=1




Các macro __NARG_I_dường như hoàn toàn không cần thiết và thừa. Nó chỉ thêm một bước nữa, và nhầm lẫn. Tôi khuyên bạn nên xóa nó hoàn toàn và chỉ xác định __NARG__thay vào đó là : #define __NARG__(...) __ARG_N(__VA_ARGS__,__RSEQ_N()).
Gabriel Staples

Hoặc bằng cách nào đó sẽ phá vỡ tiền xử lý? Tui bỏ lỡ điều gì vậy?
Gabriel Staples

Tương tự với _VFUNC_: chỉ cần xóa nó. Sau đó, xác định _VFUNClà: #define _VFUNC(name, n) name##nthay vì #define _VFUNC(name, n) _VFUNC_(name, n).
Gabriel Staples

15

Tôi chỉ đang nghiên cứu điều này bản thân mình, và tôi đã bắt gặp điều này ở đây . Tác giả đã thêm hỗ trợ đối số mặc định cho các hàm C thông qua các macro.

Tôi sẽ cố gắng tóm tắt ngắn gọn bài viết. Về cơ bản, bạn cần xác định một macro có thể đếm các đối số. Macro này sẽ trả về 2, 1, 0 hoặc bất kỳ phạm vi đối số nào mà nó có thể hỗ trợ. Ví dụ:

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

Với điều này, bạn cần tạo một macro khác có số lượng đối số thay đổi, đếm các đối số và gọi macro phù hợp. Tôi đã lấy macro ví dụ của bạn và kết hợp nó với ví dụ của bài viết. Tôi có hàm gọi FOO1 a () và hàm gọi FOO2 a với đối số b (rõ ràng, tôi giả sử C ++ ở đây, nhưng bạn có thể thay đổi macro thành bất cứ điều gì).

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

Vì vậy, nếu bạn có

FOO(a)
FOO(a,b)

Bộ tiền xử lý mở rộng ra để

a();
a(b);

Tôi chắc chắn sẽ đọc bài viết mà tôi liên kết. Nó rất nhiều thông tin và ông đề cập rằng NARG2 sẽ không hoạt động với các đối số trống. Anh ấy theo dõi điều này ở đây .


7

Đây là một phiên bản nhỏ gọn hơn của câu trả lời ở trên . Với ví dụ.

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

Chạy:

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

Lưu ý rằng có cả hai _OVR_OVR_EXPANDcó thể trông dư thừa, nhưng bộ xử lý trước cần phải mở rộng _COUNT_ARGS(__VA_ARGS__)phần, nếu không thì được coi là một chuỗi.


Tôi thích giải pháp này. Nó có thể được sửa đổi để xử lý một macro quá tải không có đối số không?
Andrew


2

Đây là một câu trả lời từ câu trả lời của Evgeni Sergeev. Điều này cũng hỗ trợ quá tải đối số bằng không !

Tôi đã thử nghiệm điều này với GCC và MinGW. Nó nên hoạt động với các phiên bản cũ và mới của C ++. Lưu ý rằng tôi sẽ không đảm bảo nó cho MSVC ... Nhưng với một số điều chỉnh, tôi tin chắc rằng nó cũng có thể được thực hiện để làm việc với điều đó.

Tôi cũng định dạng cái này để dán vào một tệp tiêu đề (mà tôi gọi là macroutil.h). Nếu bạn làm điều đó, bạn chỉ có thể bao gồm tiêu đề này bất cứ điều gì bạn cần tính năng, và không nhìn vào sự khó chịu liên quan đến việc thực hiện.

#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

Điều này dường như hoạt động tốt trên GCC, Clang và MSVC. Đây là phiên bản đã được làm sạch của một số câu trả lời ở đây

#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 Làm cách nào để điều chỉnh macro này để nó hoạt động với đối số bằng 0 #define func0() foo? Phiên bản hiện tại không xử lý trường hợp này không may.
Jerry Ma
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.