Câu trả lời của Leushenko thực sự rất tuyệt - chỉ: foo
ví dụ không biên dịch với GCC, mà không thành công foo(7)
, vấp phải FIRST
macro và cuộc gọi chức năng thực tế ( (_1, __VA_ARGS__)
, còn lại với dấu phẩy dư thừa. Ngoài ra, chúng tôi gặp rắc rối nếu chúng tôi muốn cung cấp thêm quá tải , nhu lafoo(double)
.
Vì vậy, tôi quyết định xây dựng câu trả lời xa hơn một chút, bao gồm cho phép quá tải khoảng trống (foo(void)
- gây ra khá nhiều rắc rối ...).
Ý tưởng bây giờ là: Xác định nhiều hơn một chung trong các macro khác nhau và cho phép chọn đúng theo số lượng đối số!
Số lượng đối số khá dễ dàng, dựa trên câu trả lời này :
#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)
#define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
#define CONCAT(X, Y) CONCAT_(X, Y)
#define CONCAT_(X, Y) X ## Y
Điều đó thật tuyệt, chúng tôi giải quyết một trong hai SELECT_1
hoặc SELECT_2
(hoặc nhiều đối số hơn, nếu bạn muốn / cần chúng), vì vậy chúng tôi chỉ cần định nghĩa phù hợp:
#define SELECT_0() foo_void
#define SELECT_1(_1) _Generic ((_1), \
int: foo_int, \
char: foo_char, \
double: foo_double \
)
#define SELECT_2(_1, _2) _Generic((_1), \
double: _Generic((_2), \
int: foo_double_int \
) \
)
OK, tôi đã thêm quá tải khoảng trống rồi - tuy nhiên, cái này thực sự không được bao phủ bởi tiêu chuẩn C, không cho phép các đối số dao động trống, tức là chúng tôi dựa vào các phần mở rộng của trình biên dịch !
Đầu tiên, một cuộc gọi macro trống ( foo()
) vẫn tạo ra một mã thông báo, nhưng một cuộc gọi trống. Vì vậy, macro đếm thực sự trả về 1 thay vì 0 ngay cả trong lệnh gọi macro trống. Chúng tôi có thể "dễ dàng" loại bỏ vấn đề này, nếu chúng tôi đặt dấu phẩy sau __VA_ARGS__
điều kiện , tùy thuộc vào danh sách có trống hay không:
#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)
Điều đó có vẻ dễ dàng, nhưng COMMA
macro là khá nặng nề; may mắn thay, chủ đề đã được đề cập trong một blog của Jens Gustyt (cảm ơn, Jens). Thủ thuật cơ bản là các macro chức năng không được mở rộng nếu không được theo dấu ngoặc đơn, để giải thích thêm, hãy xem blog của Jens ... Chúng ta chỉ cần sửa đổi các macro một chút theo nhu cầu của mình (Tôi sẽ sử dụng tên ngắn hơn và ít tranh luận cho sự ngắn gọn).
#define ARGN(...) ARGN_(__VA_ARGS__)
#define ARGN_(_0, _1, _2, _3, N, ...) N
#define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 1, 0)
#define SET_COMMA(...) ,
#define COMMA(...) SELECT_COMMA \
( \
HAS_COMMA(__VA_ARGS__), \
HAS_COMMA(__VA_ARGS__ ()), \
HAS_COMMA(SET_COMMA __VA_ARGS__), \
HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
)
#define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
#define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3
#define COMMA_0000 ,
#define COMMA_0001
#define COMMA_0010 ,
// ... (all others with comma)
#define COMMA_1111 ,
Và bây giờ chúng tôi ổn ...
Mã hoàn chỉnh trong một khối:
/*
* demo.c
*
* Created on: 2017-09-14
* Author: sboehler
*/
#include <stdio.h>
void foo_void(void)
{
puts("void");
}
void foo_int(int c)
{
printf("int: %d\n", c);
}
void foo_char(char c)
{
printf("char: %c\n", c);
}
void foo_double(double c)
{
printf("double: %.2f\n", c);
}
void foo_double_int(double c, int d)
{
printf("double: %.2f, int: %d\n", c, d);
}
#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)
#define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
#define CONCAT(X, Y) CONCAT_(X, Y)
#define CONCAT_(X, Y) X ## Y
#define SELECT_0() foo_void
#define SELECT_1(_1) _Generic ((_1), \
int: foo_int, \
char: foo_char, \
double: foo_double \
)
#define SELECT_2(_1, _2) _Generic((_1), \
double: _Generic((_2), \
int: foo_double_int \
) \
)
#define ARGN(...) ARGN_(__VA_ARGS__)
#define ARGN_(_0, _1, _2, N, ...) N
#define NARG(...) ARGN(__VA_ARGS__ COMMA(__VA_ARGS__) 3, 2, 1, 0)
#define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 0)
#define SET_COMMA(...) ,
#define COMMA(...) SELECT_COMMA \
( \
HAS_COMMA(__VA_ARGS__), \
HAS_COMMA(__VA_ARGS__ ()), \
HAS_COMMA(SET_COMMA __VA_ARGS__), \
HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
)
#define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
#define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3
#define COMMA_0000 ,
#define COMMA_0001
#define COMMA_0010 ,
#define COMMA_0011 ,
#define COMMA_0100 ,
#define COMMA_0101 ,
#define COMMA_0110 ,
#define COMMA_0111 ,
#define COMMA_1000 ,
#define COMMA_1001 ,
#define COMMA_1010 ,
#define COMMA_1011 ,
#define COMMA_1100 ,
#define COMMA_1101 ,
#define COMMA_1110 ,
#define COMMA_1111 ,
int main(int argc, char** argv)
{
foo();
foo(7);
foo(10.12);
foo(12.10, 7);
foo((char)'s');
return 0;
}