C ++ 전 처리기 __VA_ARGS__ 인수 개수


99

인터넷에서 답을 찾을 수 없었던 간단한 질문입니다. 가변 인수 매크로에서 인수 수를 찾는 방법은 무엇입니까? 해결책이 있다면 부스트 전처리기로 괜찮습니다.

차이가 나는 경우 추가 재 처리를 위해 전 처리기 시퀀스, 목록 또는 배열을 향상시키기 위해 가변 개수의 매크로 인수를 변환하려고합니다.


명확하게 말하면-가변 C 함수를 만드는 데 사용되는 매크로가 아니라 가변 매크로에 대해 질문하고 있습니까?

2
같은 유형의 인수입니까? 그렇다면 유형이 알려진 경우 복합 리터럴을 통한 표준 C 솔루션이 있습니다. 알 수없는 경우 __typeof__적어도 일부 컴파일러에서 작동 하도록 사용할 수 있습니다
Christoph

1
토론은 Boost 전 처리기 시퀀스 등에 관한 것이므로 C ++이어야합니다 (이것이 Q를 다시 태그 한 이유이지만 질문 제목을 변경하지 못했습니다) ... 죄송합니다; 내가 고칠 게.
Jonathan Leffler

@JonathanLeffler 사실, Boost는 C ++ 라이브러리입니다. 그러나 Boost.Preprocessor는 C와 함께 사용할 수 있습니다. AFAIK가 사용하는 것은 C ++ 전용입니다.
Justin

답변:


90

이것은 실제로 컴파일러에 따라 다르며 어떤 표준에서도 지원되지 않습니다.

그러나 여기 에 계산을 수행 하는 매크로 구현 이 있습니다.

#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

....하지만 이제는 C ++ 0x의 표준이며, 손상된 호출로부터 varadic 함수를 보호 할 수있는 좋은 방법을 허용하기 때문에 더 오래 전 이었어 야합니다. 카운트를 얻는 내가 사용하는 데 사용,하지만 난 sizeof 연산자가 .. 너무 일할 수있는 것 같아요
osirisgothra

답변은 다른 사이트로 연결됩니다. 또한 링크가 정답을 가리 키지 않는 것 같습니다. 그리고 내가 의도 한 답을 찾았더라도 컴파일 될 하드 코딩 된 "-1"을 포함하고 있기 때문에 그것은 형편없는 답으로 보인다. 더 나은 방법이 있습니다.
ceztko 2014

2
감사! 이 나를 위해 비주얼 스튜디오 2013에서 일 : #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()0을 반환하지 않습니다 . GET_ARG_COUNT()& Y_TUPLE_SIZE()솔루션이 작동합니다.
PSkocik

1
" PP_NARG()fails to return 0"... 반드시 문제가되는 것은 아닙니다. 하나는 말할 수 PP_NARG() 있어야 같은 이유로 1을 반환 PP_NARG(,)2. 반환해야 감지 토큰 첫 번째 pasteable 할 것을 요구 (실제로 어떤 경우에 편리 할 수 있습니다 0,하지만 솔루션 중 하나를 덜 일반적인 것 같다, 어떤을 할 수있다 거나 하지 않을 수 좋아 사용하는 용도에 따라) 또는 특정 구현 (예 : gnu의 쉼표 제거-붙여 넣기 트릭 필요).
H Walters

100

일반적으로이 매크로를 사용하여 여러 매개 변수를 찾습니다.

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

전체 예 :

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

완전히 유효한 C99 코드입니다. 하지만 한 가지 단점이 있습니다. SUM()매개 변수 없이는 매크로 를 호출 할 수 없지만 GCC에는 이에 대한 해결책이 있습니다 . 여기를 참조 하십시오 .

따라서 GCC의 경우 다음과 같이 매크로를 정의해야합니다.

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

빈 매개 변수 목록에서도 작동합니다.


4
UM, OP에서는 작동하지 않으며 컴파일 타임에 실행되는 BOOST_PP의 크기가 필요합니다.
Kornel Kisielewicz

5
영리한! 또한 언제 작동합니까 sizeof(int) != sizeof(void *)?
Adam Liss

3
@Kornel 다른 매크로와 마찬가지로 컴파일 타임에 평가됩니다. Boost에 대해 잘 모르겠지만 어쨌든 Boost는 필요하지 않습니다.
qrdl

4
내가 캐스팅 때문에 @ 아담은 {__VA_ARGS__}int[], 그냥입니다 int[]에 관계없이 실제 내용의,__VA_ARGS__
qrdl

3
우아한 솔루션! VS2017에서 작동합니다. 는 ##빈으로 VS2017에서 필요하지 않은 __VA_ARGS__자동으로 앞의 쉼표를 제거합니다.
포비

37

C ++ 11을 사용하고 있고 C ++ 컴파일 타임 상수로 값이 필요한 경우 매우 우아한 솔루션은 다음과 같습니다.

#include <tuple>

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

참고 : 계산은 전적으로 컴파일 시간에 발생하며 컴파일 시간 정수가 필요할 때마다 값을 사용할 수 있습니다 (예 : std :: array에 대한 템플릿 매개 변수).


2
훌륭한 솔루션! 그리고 sizeof((int[]){__VA_ARGS__})/sizeof(int)위에서 제안한 것과 달리 인수를 모두 캐스트 할 수없는 경우에도 작동합니다 int.
Wim

동의합니다. 훌륭한 솔루션! ++.
davernator

템플릿에서는 작동하지 않습니다. 예를 들어 NUMARGS (sum <1,2>); godbolt.org/z/_AAxmL
jorgbrown

1
나는 ... 그 생각 힘이 실제로 @jorgbrown, 대부분의 경우 최소한이 올 것 곳의 찬성 포인트합니다. 계산을 수행하기 위해 전 처리기 대신 컴파일러에 의존하기 때문에 대부분의 프로그래머가 기대하는 것과 일치 할 가능성이있는 컴파일러에서 보는 인수의 수를 제공합니다. 그것은 것입니다 당신이 계정으로 처리기 탐욕을 기대한다면, 그래도 문제가 발생합니다.
Justin Time-Monica 복원

훌륭한 대답입니다. 매크로에 넣을 수 있습니다#define NUM_ARGS(...) std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value
Richard Whitehead

23

편의를 위해 0 ~ 70 개의 인수에 대해 작동하고 Visual Studio, GCC 및 Clang 에서 작동하는 구현이 있습니다 . Visual Studio 2010 이상에서 작동 할 것이라고 생각하지만 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 Microsoft 변형은 인수가 없으면 실패합니다.
Vroomfondel 2018

@Vroomfondel Microsoft 변형은 0 인수에 대해 작동합니다. 위 예제의 첫 번째 static_assert는 인수가없는 경우에 대한 특정 테스트이며 방금 Visual Studio 2017 v15.8.9에서 컴파일하고 실행했습니다.
Chris Kline 2018

흥미롭게도-Microsoft가 아닌 컴파일러에서 Microsoft 변형을 사용하는 것은 작동하지 않습니다. M $ 전처리 기가 다른 방식으로 작동하여 코드가 반대로 작동하는 것을 알고 있습니까? BTW 나는 C ++가 아닌 C를 시도했다.
Vroomfondel 2018

MSVC가 "길이가 0 인 __VA_ARGS__"(C ++에서 C ++ 20까지 기술적으로 거의 보편적이고 사실상 표준 인) 컴파일러 확장 에 대해 조금 더 좋기 때문이라고 생각합니다 . 대부분의 (모두?) 컴파일러는 길이가 0을 허용하지만 목록 비어있는 경우 후행 쉼표에 질식합니다 ( 이 경우 쉼표를 제거하기 위해 ##proto-로 오버로드 됨 __VA_OPT__). 확장의 MSVC의 버전은 쉼표에 질식하지 않습니다 (하지만 것입니다 오버로드에 질식 ##). MSVC unused, __VA_ARGS__를 비 MSVC 와 비교하십시오 0, ## __VA_ARGS__. 둘 다 더 정확하지 않습니다. 문제는 그들이 다르다는 것입니다.
저스틴 시간 - 분석 재개 모니카

최근 초안에 대한 책갈피를 잃어 버렸기 때문에 이것이 C에서도 동일한 지 확실하지 않습니다. @Vroomfondel.
저스틴 시간 - 분석 재개 모니카

11

컴파일 타임에 인수 수를 찾는 몇 가지 C ++ 11 솔루션이 있지만 아무도 다음과 같이 간단한 것을 제안하지 않았다는 사실에 놀랐습니다.

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

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

<tuple>헤더를 포함 할 필요도 없습니다 .


1
"하지만 왜 가변 템플릿과 sizeof ...를 사용하지 않는지 (내 자신의 대답에서와 같이)" C ++는 괴물이되었습니다. 너무 많은 기능이 있으며 가변 템플릿과 같은 많은 기능이 거의 사용되지 않습니다. 당신은 그것에 대해 읽고 몇 가지 예를 쓴 다음 잊어 버립니다. 따라서 적시에 올바른 아이디어를내는 것은 어렵습니다. 귀하의 솔루션이 내 것보다 더 나은 옵션 인 것 같으므로 자연 선택이 작동하도록하고 내 솔루션을 삭제합니다.
zdf

1
@ZDF는 이해할 수 있지만 가변 템플릿을 지속적으로 사용합니다. 내 프로그램은 C ++ 11 이후로 훨씬 더 강력 해졌으며 이것이 주된 이유 중 하나입니다. 답변을 삭제할 필요는 없습니다.
monkey0506

1
같은 smth에서는 작동하지 않습니다 VA_COUNT(&,^,%). 또한 기능을 통해 계산하는 경우 매크로를 만드는 데 아무런 의미가 없습니다.
Qwertiy

이 솔루션은 질문으로 남아 있습니다. VA_COUNT의 매개 변수는 아직 변수 등으로 정의되지 않은 모든 식별자이며 '*** 변수가 정의되지 않았습니다'라는 오류가 발생합니다. 이 문제를 해결할 방법이 있습니까?
ipid

7

이것은 gcc / llvm에서 0 개의 인수로 작동합니다. [링크는 멍청하다]

/*
 * 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는 빈 인수를 사용하는 데 사용되는 ## 연산자를 무시하는 것 같습니다. 아마도 다음과 같은 것으로 해결할 수 있습니다.

#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

Visual Studio 2008에서 이것을 테스트했는데 0 개의 인수 COUNT_ARGS () = 1에서는 작동하지 않았습니다.
user720594 dec

링크가 끊어진 것 같습니다.
Jan Smrčina

고정 링크. VS는 평소와 다른 일을해야합니다. :). 나는 그들이 조만간 C99를 완전히 지원할 것이라고 생각하지 않습니다.
user1187902

2
Er, ##__VA_ARGS__if __VA_ARGS__가 비어 있기 전에 쉼표를 먹는 것은 GCC 확장입니다. 표준적인 행동이 아닙니다.
기금 모니카의 소송

6

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

0-32 개의 인수에 대해 작동합니다. 이 제한은 쉽게 확장 할 수 있습니다.

편집 : 단순화 된 버전 (VS2015 14.0.25431.01 업데이트 3 및 gcc 7.4.0에서 작동) 최대 100 개의 인수를 복사 및 붙여 넣기 :

#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
나만 그런 건가 아니면 코드 냄새 규칙을 어기는 건가 ..?
osirisgothra

최소 VS2012까지의 VC ++와 기본 테스트에서 GCC 및 clang으로 작동합니다.
ThreeBit 2014 년

@osirisgothra, 정확히 왜 냄새가나요?
ceztko 2014

이 매크로는 광범위한 컴파일러 지원을 제공하지만, 같은 문자열과 같은 매크로 인수와 함께 작동하지 않아 Y_TUPLE_SIZE("Hello")실행이 불가능합니다. @osirisgothra에 동의합니다.
ceztko 2014-12-15

1
이 매크로는 작동 할 수 있지만 심각한 결함이 있습니다. 저는 많은 연구를했고 GCC와 VS에서 작동하는 더 깨끗한 접근 방식을 찾았습니다. 비슷한 질문에 대한 내 대답 에서 찾을 수 있습니다 .
ceztko 2015

3

VA_ARGS에 대한 각 인수 가 쉼표로 구분 된다고 가정합니다 . 그렇다면이 작업을 수행하는 꽤 깨끗한 방법으로 작동해야한다고 생각합니다.

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

clang 4 및 GCC 5.1의 godbolt에서 나를 위해 일했습니다. 이것은 컴파일 타임에 계산되지만 전처리기에 대해서는 평가되지 않습니다. 따라서 FOR_EACH 만들기와 같은 작업을 시도하는 경우 작동하지 않습니다.


이 답변은 과소 평가되었습니다. 에도 작동합니다 NUMARGS(hello, world = 2, ohmy42, !@#$%^&*()-+=)!!! 각 인수 문자열은 일부 다른 기호와 같은 가질 수 ','있지만
pterodragon

int count = NUMARGS( foo(1, 2) );1이 아닌 2를 생성 하기 때문에 괄호를 조정해야합니다 . godbolt.org/z/kpBuOm
jorgbrown

이것은 람다, 함수 호출 또는 매개 변수에 추가 쉼표를 포함 할 수있는 다른 모든 경우에는 예상대로 작동하지 않습니다.
Nandee 2010 년

2

여기에서는 VA_ARGS의 0 개 이상의 인수를 계산하는 간단한 방법입니다 . 제 예에서는 최대 5 개의 변수를 가정하지만 원하는 경우 더 추가 할 수 있습니다.

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

불행히도이 접근 방식은 VA_ARGS_NUM매크로와 함께 사용될 때 잘못 작동합니다 . 만약 내가 #define TEST(즉, 비어 TEST있고 VA_ARGS_NUM(TEST)) 다음에서 사용될 때 0 (영)을 반환하지 않는다면 #if:(
AntonK

@AntonK 당신이 한 일을 정확하게 게시 할 수 있습니까?
elhadi dp ıpɐɥ ן ǝ

0

토큰을 문자열 화하고 계산할 수 있습니다.

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
이 답변에 대해 보류중인 편집을 살펴 보았습니다. 두 개의 계정이있는 것 같습니다. 하나를 고수하면 승인을받지 않고 자신의 게시물을 편집 할 수 있습니다.
J Richard Snape 2015 년

0

Boost Preprocessor는 실제로 Boost 1.49부터 BOOST_PP_VARIADIC_SIZE(...). 크기 64까지 작동합니다.

내부적으로는 Kornel Kisielewicz의 대답 과 기본적으로 동일 합니다.


@CarloWood 실제로. 전처리 기는 실제로 "인수 0 개"라는 개념을 가지고 있지 않습니다. 우리가 "제로 인수"라고 생각하는 것은 전 처리기의 "하나의 빈 인수"입니다. 그러나 이전 쉼표 __VA_OPT__##__VA_ARGS__제거하기 위해 C ++ 20 또는 컴파일러 확장을 사용하여 수정할 수 있습니다 . 예 : godbolt.org/z/X7OvnK
Justin

0

여기에 대한 답변이 아직 불완전하다는 것을 발견했습니다.

여기에서 찾은 가장 가까운 이식 가능한 구현은 다음과 같습니다. C ++ 전 처리기 __VA_ARGS__ 인수 수

그러나 최소한 -std=gnu++11명령 줄 매개 변수 없이는 GCC에서 0 인수로 작동하지 않습니다 .

그래서이 솔루션을 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.