Bộ tiền xử lý C ++ __VA_ARGS__ số đối số


99

Câu hỏi đơn giản mà tôi không thể tìm thấy câu trả lời trên mạng. Trong macro đối số khác nhau, làm thế nào để tìm số đối số? Tôi không sao với bộ tiền xử lý tăng cường, nếu nó có giải pháp.

Nếu nó tạo ra sự khác biệt, tôi đang cố gắng chuyển đổi số lượng đối số macro có thể thay đổi để thúc đẩy chuỗi, danh sách hoặc mảng tiền xử lý để xử lý lại thêm.


Chỉ cần nói rõ - bạn đang hỏi về macro đa dạng và không phải macro được sử dụng để tạo các hàm C đa dạng?

2
các đối số có cùng kiểu không? nếu vậy, và nếu loại được biết, có một dung dịch C tiêu chuẩn thông qua các ký tự hợp chất; nếu nó không đúng, bạn có thể sử dụng __typeof__để làm cho nó làm việc ít nhất là trên một số trình biên dịch
Christoph

1
Vì cuộc thảo luận là về trình tự tiền xử lý Boost, v.v., nó phải là C ++ (đó là lý do tại sao tôi đã gắn thẻ lại Q - nhưng không thể thay đổi tiêu đề câu hỏi) ... Rất tiếc; Tôi sẽ sửa nó.
Jonathan Leffler

@JonathanLeffler Đúng, Boost là một thư viện C ++. Tuy nhiên, Boost.Preprocessor có thể được sử dụng với C. AFAIK, không có gì nó sử dụng là C ++ cụ thể.
Justin

Câu trả lời:


90

Điều này thực sự phụ thuộc vào trình biên dịch và không được hỗ trợ bởi bất kỳ tiêu chuẩn nào.

Tuy nhiên, ở đây, bạn có một triển khai macro có giá trị:

#define PP_NARG(...) \
         PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
         PP_ARG_N(__VA_ARGS__)
#define PP_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 PP_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

/* Some test cases */


PP_NARG(A) -> 1
PP_NARG(A,B) -> 2
PP_NARG(A,B,C) -> 3
PP_NARG(A,B,C,D) -> 4
PP_NARG(A,B,C,D,E) -> 5
PP_NARG(1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3) -> 63

.... nhưng bây giờ là tiêu chuẩn trong C ++ 0x và lẽ ra đã lâu hơn vì nó cho phép một cách tuyệt vời để bảo vệ các hàm khác nhau khỏi các lệnh gọi bị hỏng (tức là bạn có thể chuyển các giá trị sau các mục khác nhau. Đây thực sự là một cách nhận được số lượng i sử dụng để sử dụng, nhưng tôi đoán sizeof có thể làm việc quá ..
osirisgothra

Câu trả lời liên kết đến một trang web khác. Ngoài ra, liên kết dường như không trỏ đến câu trả lời chính xác. Và ngay cả khi tôi cố gắng tìm ra câu trả lời dự định, nó có vẻ là một câu trả lời kém vì nó nhúng một mã cứng "-1" sẽ được biên dịch. Có nhiều phương pháp tốt hơn.
ceztko

2
Cảm ơn! điều này đã hoạt động trong Visual Studio 2013 đối với tôi: #define EXPAND(x) x #define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,_9,N,...) N #define PP_NARG(...) EXPAND(PP_ARG_N(__VA_ARGS__, 9,8,7,6,5,4,3,2,1,0))`` '
mchiasson

1
PP_NARG()không trả lại được 0. GET_ARG_COUNT()& Y_TUPLE_SIZE()giải pháp hoạt động.
PSkocik,

1
" PP_NARG()không trả về 0" ... không nhất thiết là một vấn đề. Người ta có thể nói rằng PP_NARG() nên trở lại 1 với cùng lý do PP_NARG(,)nên trả lại 2. Phát hiện 0 thực sự có thể có ích trong một số trường hợp, nhưng các giải pháp dường như thể là ít chung (đòi hỏi rằng đầu tiên thẻ được pasteable; mà có thể hoặc không thể được chấp nhận tùy thuộc vào việc bạn đang sử dụng nó để làm gì) hoặc triển khai cụ thể (chẳng hạn như yêu cầu thủ thuật xóa-dán dấu phẩy của gnu).
H Walters

100

Tôi thường sử dụng macro này để tìm một số tham số:

#define NUMARGS(...)  (sizeof((int[]){__VA_ARGS__})/sizeof(int))

Ví dụ đầy đủ:

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#define NUMARGS(...)  (sizeof((int[]){__VA_ARGS__})/sizeof(int))
#define SUM(...)  (sum(NUMARGS(__VA_ARGS__), __VA_ARGS__))

void sum(int numargs, ...);

int main(int argc, char *argv[]) {

    SUM(1);
    SUM(1, 2);
    SUM(1, 2, 3);
    SUM(1, 2, 3, 4);

    return 1;
}

void sum(int numargs, ...) {
    int     total = 0;
    va_list ap;

    printf("sum() called with %d params:", numargs);
    va_start(ap, numargs);
    while (numargs--)
        total += va_arg(ap, int);
    va_end(ap);

    printf(" %d\n", total);

    return;
}

Đây là mã C99 hoàn toàn hợp lệ. Tuy nhiên, nó có một nhược điểm - bạn không thể gọi macro SUM()mà không có tham số, nhưng GCC có một giải pháp cho nó - xem tại đây .

Vì vậy, trong trường hợp GCC, bạn cần xác định các macro như sau:

#define       NUMARGS(...)  (sizeof((int[]){0, ##__VA_ARGS__})/sizeof(int)-1)
#define       SUM(...)  sum(NUMARGS(__VA_ARGS__), ##__VA_ARGS__)

và nó sẽ hoạt động ngay cả với danh sách tham số trống


4
UM, nó sẽ không hoạt động cho OP, anh ấy cần kích thước cho BOOST_PP chạy theo thời gian biên dịch.
Kornel Kisielewicz

5
Tài giỏi! Nó cũng hoạt động khi sizeof(int) != sizeof(void *)nào?
Adam Liss

3
@Kornel Giống như bất kỳ macro nào, nó được đánh giá tại thời điểm biên dịch. Tôi không biết gì về Boost, nhưng dù sao thì Boost không cần thiết.
qrdl

4
@Adam Bởi vì tôi truyền {__VA_ARGS__}đến int[], nó chỉ là int[], bất kể nội dung thực tế của__VA_ARGS__
qrdl

3
Giải pháp thanh lịch! Hoạt động trong VS2017. Các ##không cần thiết trong VS2017 như một sản phẩm nào __VA_ARGS__sẽ tự động loại bỏ bất kỳ dấu phẩy trước.
poby

37

Nếu bạn đang sử dụng C ++ 11 và bạn cần giá trị dưới dạng hằng số thời gian biên dịch C ++, một giải pháp rất hữu ích là:

#include <tuple>

#define MACRO(...) \
    std::cout << "num args: " \
    << std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value \
    << std::endl;

Xin lưu ý: việc đếm diễn ra hoàn toàn tại thời điểm biên dịch và giá trị có thể được sử dụng bất cứ khi nào yêu cầu số nguyên thời gian biên dịch, ví dụ như tham số mẫu cho std :: array.


2
Giải pháp tuyệt vời! Và không giống như sizeof((int[]){__VA_ARGS__})/sizeof(int)đề xuất ở trên, nó hoạt động ngay cả khi tất cả các đối số không thể được chuyển thành int.
Wim

Đã đồng ý. Giải pháp tuyệt vời! ++.
davernator

Không hoạt động với các mẫu, tức là NUMARGS (sum <1,2>); xem godbolt.org/z/_AAxmL
jorgbrown

1
Tôi ... nghĩ rằng đó thực sự có thể là một điểm có lợi cho nó, @jorgbrown, ít nhất là trong hầu hết các trường hợp khi nó xuất hiện. Vì nó dựa vào trình biên dịch thay vì bộ xử lý trước để thực hiện việc đếm, nên nó cung cấp số lượng các đối số mà trình biên dịch nhìn thấy, có thể sẽ khớp với những gì mà hầu hết các lập trình viên mong đợi. Tuy nhiên, nó sẽ gây ra rắc rối nếu bạn mong đợi nó tính đến tính tham lam của bộ xử lý trước.
Justin Time - Phục hồi Monica

Câu trả lời tuyệt vời. Bạn có thể đặt nó vào macro#define NUM_ARGS(...) std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value
Richard Whitehead

23

Để thuận tiện, đây là một triển khai hoạt động cho 0 đến 70 đối số và hoạt động trong Visual Studio, GCC và Clang . Tôi tin rằng nó sẽ hoạt động trong Visual Studio 2010 trở lên, nhưng mới chỉ thử nghiệm nó trong VS2013.

#ifdef _MSC_VER // Microsoft compilers

#   define GET_ARG_COUNT(...)  INTERNAL_EXPAND_ARGS_PRIVATE(INTERNAL_ARGS_AUGMENTER(__VA_ARGS__))

#   define INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__
#   define INTERNAL_EXPAND(x) x
#   define INTERNAL_EXPAND_ARGS_PRIVATE(...) INTERNAL_EXPAND(INTERNAL_GET_ARG_COUNT_PRIVATE(__VA_ARGS__, 69, 68, 67, 66, 65, 64, 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))
#   define INTERNAL_GET_ARG_COUNT_PRIVATE(_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, _64, _65, _66, _67, _68, _69, _70, count, ...) count

#else // Non-Microsoft compilers

#   define GET_ARG_COUNT(...) INTERNAL_GET_ARG_COUNT_PRIVATE(0, ## __VA_ARGS__, 70, 69, 68, 67, 66, 65, 64, 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)
#   define INTERNAL_GET_ARG_COUNT_PRIVATE(_0, _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, _64, _65, _66, _67, _68, _69, _70, count, ...) count

#endif

static_assert(GET_ARG_COUNT() == 0, "GET_ARG_COUNT() failed for 0 arguments");
static_assert(GET_ARG_COUNT(1) == 1, "GET_ARG_COUNT() failed for 1 argument");
static_assert(GET_ARG_COUNT(1,2) == 2, "GET_ARG_COUNT() failed for 2 arguments");
static_assert(GET_ARG_COUNT(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, 64, 65, 66, 67, 68, 69, 70) == 70, "GET_ARG_COUNT() failed for 70 arguments");

IMHO biến thể Microsoft không thành công với không đối số.
Vroomfondel

@Vroomfondel, biến thể của Microsoft hoạt động không đối số. Static_assert đầu tiên trong ví dụ trên là một thử nghiệm cụ thể cho trường hợp không đối số và tôi vừa biên dịch và chạy nó trên Visual Studio 2017 v15.8.9.
Chris Kline

Thật thú vị - việc sử dụng biến thể của Microsoft trên trình biên dịch không phải của Microsoft không hoạt động - bạn có biết bộ tiền xử lý M $ làm gì khác khiến mã hoạt động theo cách ngược lại không? BTW Tôi đã thử C, không phải C ++;
Vroomfondel

Tôi tin rằng đó là bởi vì MSVC tốt hơn một chút về "zero-length __VA_ARGS__" (trong C ++, về mặt kỹ thuật là một phần mở rộng trình biên dịch (nigh-Universal, de facto standard) cho đến C ++ 20). Hầu hết (? Tất cả) các trình biên dịch cho phép zero-length, nhưng nghẹt thở trên dấu phẩy dấu nếu danh sách rỗng (và tình trạng quá tải ##như một proto- __VA_OPT__, để loại bỏ các dấu phẩy trong trường hợp này); Phiên bản tiện ích mở rộng của MSVC không bị nghẹn vì dấu phẩy (nhưng sẽ bị nghẹt khi quá tải ##). So sánh MSVC unused, __VA_ARGS__với không MSVC 0, ## __VA_ARGS__; không đúng hơn, vấn đề là chúng khác nhau.
Justin Time - Khôi phục Monica

Tuy nhiên, tôi không chắc điều này có giống trong C hay không, @Vroomfondel, vì tôi đã đánh mất dấu trang vào bản nháp gần đây nhất.
Thời gian Justin - Phục hồi Monica

11

Có một số giải pháp C ++ 11 để tìm số lượng đối số tại thời điểm biên dịch, nhưng tôi ngạc nhiên khi thấy rằng không ai đề xuất bất cứ điều gì đơn giản như:

#define VA_COUNT(...) detail::va_count(__VA_ARGS__)

namespace detail
{
    template<typename ...Args>
    constexpr std::size_t va_count(Args&&...) { return sizeof...(Args); }
}

Điều này cũng không yêu cầu bao gồm <tuple>tiêu đề.


1
"nhưng tại sao không chỉ sử dụng một mẫu đa dạng và sizeof ... thay vào đó (như trong câu trả lời của riêng tôi)" c ++ đã trở thành một con quái vật. Nó có quá nhiều tính năng và nhiều trong số chúng, như các mẫu khác nhau, hiếm khi được sử dụng. Bạn đọc về nó, bạn viết một số ví dụ và sau đó bạn quên nó đi. Vì vậy, thật khó để đưa ra ý tưởng phù hợp vào đúng thời điểm. Vì giải pháp của bạn có vẻ là một lựa chọn tốt hơn của tôi, tôi sẽ để cho chọn lọc tự nhiên hoạt động và tôi sẽ xóa giải pháp của mình.
zdf

1
@ZDF có thể hiểu được, nhưng tôi tình cờ sử dụng các mẫu khác nhau liên tục. Các chương trình của tôi đã trở nên mạnh mẽ hơn nhiều kể từ C ++ 11, và đây là một trong những lý do chính tại sao. Tôi nghĩ không cần phải xóa câu trả lời của bạn.
Monkey0506,

1
Nó sẽ không hoạt động với smth like VA_COUNT(&,^,%). Ngoài ra, nếu bạn đang đếm thông qua một funtion, tôi không thấy bất kỳ sự cố nào trong việc tạo macro.
Qwertiy

Giải pháp này vẫn còn là một câu hỏi: các tham số của VA_COUNT là tất cả các số nhận dạng chưa được xác định là một biến hoặc một cái gì đó và nó gây ra lỗi '*** biến không được xác định'. Có cách nào để sửa lỗi này không?
ipid

7

điều này hoạt động với 0 đối số với gcc / llvm. [liên kết bị lỗi]

/*
 * we need a comma at the start for ##_VA_ARGS__ to consume then
 * the arguments are pushed out in such a way that 'cnt' ends up with
 * the right count.  
 */
#define COUNT_ARGS(...) COUNT_ARGS_(,##__VA_ARGS__,6,5,4,3,2,1,0)
#define COUNT_ARGS_(z,a,b,c,d,e,f,cnt,...) cnt

#define C_ASSERT(test) \
    switch(0) {\
      case 0:\
      case test:;\
    }

int main() {
   C_ASSERT(0 ==  COUNT_ARGS());
   C_ASSERT(1 ==  COUNT_ARGS(a));
   C_ASSERT(2 ==  COUNT_ARGS(a,b));
   C_ASSERT(3 ==  COUNT_ARGS(a,b,c));
   C_ASSERT(4 ==  COUNT_ARGS(a,b,c,d));
   C_ASSERT(5 ==  COUNT_ARGS(a,b,c,d,e));
   C_ASSERT(6 ==  COUNT_ARGS(a,b,c,d,e,f));
   return 0;
}

Visual Studio dường như đang bỏ qua toán tử ## được sử dụng để sử dụng đối số trống. Bạn có thể giải quyết vấn đề đó bằng những thứ như

#define CNT_ COUNT_ARGS
#define PASTE(x,y) PASTE_(x,y)
#define PASTE_(x,y) x ## y
#define CNT(...) PASTE(ARGVS,PASTE(CNT_(__VA_ARGS__),CNT_(1,##__VA_ARGS__)))
//you know its 0 if its 11 or 01
#define ARGVS11 0
#define ARGVS01 0
#define ARGVS12 1
#define ARGVS23 2
#define ARGVS34 3

Tôi đã kiểm tra điều này cho Visual Studio 2008 và nó không hoạt động với 0 đối số COUNT_ARGS () = 1.
user720594

Liên kết dường như bị hỏng.
Jan Smrčina

liên kết cố định. VS phải làm một cái gì đó khác như thường lệ :). Tôi không nghĩ rằng họ sẽ sớm hỗ trợ C99 đầy đủ.
user1187902 13/02/16

2
Er, ##__VA_ARGS__ăn dấu phẩy trước nếu __VA_ARGS__trống là phần mở rộng GCC. Đó không phải là hành vi tiêu chuẩn.
Vụ kiện của Fund Monica

6

Với phần mở rộng msvc:

#define Y_TUPLE_SIZE(...) Y_TUPLE_SIZE_II((Y_TUPLE_SIZE_PREFIX_ ## __VA_ARGS__ ## _Y_TUPLE_SIZE_POSTFIX,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))
#define Y_TUPLE_SIZE_II(__args) Y_TUPLE_SIZE_I __args

#define Y_TUPLE_SIZE_PREFIX__Y_TUPLE_SIZE_POSTFIX ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0

#define Y_TUPLE_SIZE_I(__p0,__p1,__p2,__p3,__p4,__p5,__p6,__p7,__p8,__p9,__p10,__p11,__p12,__p13,__p14,__p15,__p16,__p17,__p18,__p19,__p20,__p21,__p22,__p23,__p24,__p25,__p26,__p27,__p28,__p29,__p30,__p31,__n,...) __n

Hoạt động cho 0-32 đối số. Giới hạn này có thể được mở rộng một cách dễ dàng.

CHỈNH SỬA: Phiên bản đơn giản hóa (hoạt động trong VS2015 14.0.25431.01 Cập nhật 3 & gcc 7.4.0) tối đa 100 đối số để sao chép và dán:

#define COUNTOF(...) _COUNTOF_CAT( _COUNTOF_A, ( 0, ##__VA_ARGS__, 100,\
    99, 98, 97, 96, 95, 94, 93, 92, 91, 90,\
    89, 88, 87, 86, 85, 84, 83, 82, 81, 80,\
    79, 78, 77, 76, 75, 74, 73, 72, 71, 70,\
    69, 68, 67, 66, 65, 64, 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 ) )
#define _COUNTOF_CAT( a, b ) a b
#define _COUNTOF_A( a0, a1, a2, a3, a4, a5, a6, a7, a8, a9,\
    a10, a11, a12, a13, a14, a15, a16, a17, a18, a19,\
    a20, a21, a22, a23, a24, a25, a26, a27, a28, a29,\
    a30, a31, a32, a33, a34, a35, a36, a37, a38, a39,\
    a40, a41, a42, a43, a44, a45, a46, a47, a48, a49,\
    a50, a51, a52, a53, a54, a55, a56, a57, a58, a59,\
    a60, a61, a62, a63, a64, a65, a66, a67, a68, a69,\
    a70, a71, a72, a73, a74, a75, a76, a77, a78, a79,\
    a80, a81, a82, a83, a84, a85, a86, a87, a88, a89,\
    a90, a91, a92, a93, a94, a95, a96, a97, a98, a99,\
    a100, n, ... ) n

4
có phải chỉ tôi hay không hay cái này phá vỡ các quy tắc về mùi mã ..?
osirisgothra

Nó hoạt động với tôi với VC ++ tối thiểu là VS2012 và GCC và tiếng kêu trong thử nghiệm cơ bản của tôi.
ThreeBit

@osirisgothra, chính xác tại sao nó có mùi?
ceztko

Mặc dù macro này có hỗ trợ nhiều trình biên dịch, nhưng nó không hoạt động với các đối số macro như một chuỗi như vậy Y_TUPLE_SIZE("Hello"), khiến nó khá khó khả thi. Tôi đồng ý với @osirisgothra.
ceztko

1
Macro này có thể làm việc cho bạn nhưng có những khiếm khuyết nghiêm trọng. Tôi đã nghiên cứu rất nhiều và tìm thấy các phương pháp tiếp cận sạch hơn phù hợp với GCC và VS. Bạn có thể tìm thấy chúng trong câu trả lời của tôi cho một câu hỏi tương tự.
ceztko

3

Tôi giả định rằng mỗi đối số cho VA_ARGS sẽ được phân tách bằng dấu phẩy. Nếu vậy, tôi nghĩ điều này sẽ hoạt động như một cách khá rõ ràng để làm điều này.

#include <cstring>

constexpr int CountOccurances(const char* str, char c) {
    return str[0] == char(0) ? 0 : (str[0] == c) + CountOccurances(str+1, c);
}

#define NUMARGS(...) (CountOccurances(#__VA_ARGS__, ',') + 1)

int main(){
    static_assert(NUMARGS(hello, world) == 2, ":(")  ;
    return 0;
}

Đã làm việc cho tôi trên chốt đỡ cho clang 4 và GCC 5.1. Điều này sẽ được tính toán tại thời điểm biên dịch, nhưng sẽ không đánh giá cho bộ tiền xử lý. Vì vậy, nếu bạn đang cố gắng làm điều gì đó như tạo FOR_EACH , thì điều này sẽ không hoạt động.


Câu trả lời này được đánh giá thấp. Nó sẽ hoạt động ngay cả cho NUMARGS(hello, world = 2, ohmy42, !@#$%^&*()-+=)!!! Mỗi chuỗi arg không thể có một số biểu tượng khác như ','
pterodragon

Cần phải được tinh chỉnh cho Parens, bởi vì int count = NUMARGS( foo(1, 2) );sản xuất 2 chứ không phải 1. godbolt.org/z/kpBuOm
jorgbrown

Điều này sẽ không hoạt động như mong đợi với lambdas, lời gọi hàm hoặc bất kỳ thứ gì khác có thể chứa thêm dấu phẩy trong các tham số.
Nandee

2

đây là một cách đơn giản để đếm 0 hoặc nhiều đối số của VA_ARGS , ví dụ của tôi giả định có tối đa 5 biến, nhưng bạn có thể thêm nhiều biến nếu muốn.

#define VA_ARGS_NUM_PRIV(P1, P2, P3, P4, P5, P6, Pn, ...) Pn
#define VA_ARGS_NUM(...) VA_ARGS_NUM_PRIV(-1, ##__VA_ARGS__, 5, 4, 3, 2, 1, 0)


VA_ARGS_NUM()      ==> 0
VA_ARGS_NUM(19)    ==> 1
VA_ARGS_NUM(9, 10) ==> 2
         ...

Đáng tiếc là phương pháp này hoạt động không chính xác khi VA_ARGS_NUMđược sử dụng với vĩ mô: nếu tôi có #define TEST(tức là trống rỗng TEST) và VA_ARGS_NUM(TEST)không trả lại 0 (zero) khi được sử dụng trong #if:(
AntonK

@AntonK bạn có thể đăng chính xác những gì bạn đã làm được không?
elhadi dp ıpɐɥ ן ǝ

0

Bạn có thể xâu chuỗi và đếm các mã thông báo:

int countArgs(char *args)
{
  int result = 0;
  int i = 0;

  while(isspace(args[i])) ++i;
  if(args[i]) ++result;

  while(args[i]) {
    if(args[i]==',') ++result;
    else if(args[i]=='\'') i+=2;
    else if(args[i]=='\"') {
      while(args[i]) {
        if(args[i+1]=='\"' && args[i]!='\\') {
          ++i;
          break;
        }
        ++i;
      }
    }
    ++i;
  }

  return result;
}

#define MACRO(...) \
{ \
  int count = countArgs(#__VA_ARGS__); \
  printf("NUM ARGS: %d\n",count); \
}

2
Chỉ cần xem qua bản chỉnh sửa đang chờ xử lý trên câu trả lời này - có vẻ như bạn có thể có hai tài khoản. Nếu bạn thích một, bạn sẽ có thể chỉnh sửa bài đăng của mình mà không cần phê duyệt.
J Richard Snape

0

Boost Preprocessor thực sự có điều này kể từ Boost 1.49, như BOOST_PP_VARIADIC_SIZE(...). Nó hoạt động đến kích thước 64.

Về cơ bản, nó giống như câu trả lời của Kornel Kisielewicz .


@CarloWood Thật vậy. Bộ tiền xử lý không thực sự có khái niệm "không đối số". Những gì chúng tôi nghĩ là "không đối số" là "một đối số trống" trong bộ tiền xử lý. Nhưng nó có thể sửa được bằng cách sử dụng C ++ 20 __VA_OPT__hoặc các phần mở rộng của trình biên dịch để ##__VA_ARGS__xóa dấu phẩy trước đó, ví dụ: godbolt.org/z/X7OvnK
Justin

0

Tôi đã tìm thấy câu trả lời ở đây vẫn chưa hoàn thành.

Cách triển khai di động gần nhất mà tôi đã tìm thấy từ đây là: Bộ tiền xử lý C ++ __VA_ARGS__ số đối số

Nhưng nó không hoạt động với các đối số 0 trong GCC mà không có ít nhất -std=gnu++11tham số dòng lệnh.

Vì vậy, tôi quyết định hợp nhất giải pháp này với: https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/

#define UTILITY_PP_CONCAT_(v1, v2) v1 ## v2
#define UTILITY_PP_CONCAT(v1, v2) UTILITY_PP_CONCAT_(v1, v2)

#define UTILITY_PP_CONCAT5_(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4

#define UTILITY_PP_IDENTITY_(x) x
#define UTILITY_PP_IDENTITY(x) UTILITY_PP_IDENTITY_(x)

#define UTILITY_PP_VA_ARGS_(...) __VA_ARGS__
#define UTILITY_PP_VA_ARGS(...) UTILITY_PP_VA_ARGS_(__VA_ARGS__)

#define UTILITY_PP_IDENTITY_VA_ARGS_(x, ...) x, __VA_ARGS__
#define UTILITY_PP_IDENTITY_VA_ARGS(x, ...) UTILITY_PP_IDENTITY_VA_ARGS_(x, __VA_ARGS__)

#define UTILITY_PP_IIF_0(x, ...) __VA_ARGS__
#define UTILITY_PP_IIF_1(x, ...) x
#define UTILITY_PP_IIF(c) UTILITY_PP_CONCAT_(UTILITY_PP_IIF_, c)

#define UTILITY_PP_HAS_COMMA(...) UTILITY_PP_IDENTITY(UTILITY_PP_VA_ARGS_TAIL(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0))
#define UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_(...) ,

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

#define UTILITY_PP_IS_EMPTY_(_0, _1, _2, _3) UTILITY_PP_HAS_COMMA(UTILITY_PP_CONCAT5_(UTILITY_PP_IS_EMPTY_IS_EMPTY_CASE_, _0, _1, _2, _3))
#define UTILITY_PP_IS_EMPTY_IS_EMPTY_CASE_0001 ,

#define UTILITY_PP_VA_ARGS_SIZE(...) UTILITY_PP_IIF(UTILITY_PP_IS_EMPTY(__VA_ARGS__))(0, UTILITY_PP_VA_ARGS_SIZE_(__VA_ARGS__, UTILITY_PP_VA_ARGS_SEQ64()))
#define UTILITY_PP_VA_ARGS_SIZE_(...) UTILITY_PP_IDENTITY(UTILITY_PP_VA_ARGS_TAIL(__VA_ARGS__))

#define UTILITY_PP_VA_ARGS_TAIL(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14, x, ...) x
#define UTILITY_PP_VA_ARGS_SEQ64() 15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0

#define EATER0(...)
#define EATER1(...) ,
#define EATER2(...) (/*empty*/)
#define EATER3(...) (/*empty*/),
#define EATER4(...) EATER1
#define EATER5(...) EATER2
#define MAC0() ()
#define MAC1(x) ()
#define MACV(...) ()
#define MAC2(x,y) whatever

static_assert(UTILITY_PP_VA_ARGS_SIZE() == 0, "1");
static_assert(UTILITY_PP_VA_ARGS_SIZE(/*comment*/) == 0, "2");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a) == 1, "3");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b) == 2, "4");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b, c) == 3, "5");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b, c, d) == 4, "6");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b, c, d, e) == 5, "7");
static_assert(UTILITY_PP_VA_ARGS_SIZE((void)) == 1, "8");
static_assert(UTILITY_PP_VA_ARGS_SIZE((void), b, c, d) == 4, "9");
static_assert(UTILITY_PP_VA_ARGS_SIZE(UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_) == 1, "10");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER0) == 1, "11");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER1) == 1, "12");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER2) == 1, "13");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER3) == 1, "14");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER4) == 1, "15");
static_assert(UTILITY_PP_VA_ARGS_SIZE(MAC0) == 1, "16");
// a warning in msvc
static_assert(UTILITY_PP_VA_ARGS_SIZE(MAC1) == 1, "17");
static_assert(UTILITY_PP_VA_ARGS_SIZE(MACV) == 1, "18");
// This one will fail because MAC2 is not called correctly
//static_assert(UTILITY_PP_VA_ARGS_SIZE(MAC2) == 1, "19");

https://godbolt.org/z/3idaKd

  • c++11, msvc 2015, gcc 4.7.1,clang 3.0
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.