GCC의 ## __ VA_ARGS__ 트릭에 대한 표준 대안?


151

잘 알려진 문제 C99에서 가변 인자 매크로 빈 인수와 함께.

예:

#define FOO(...)       printf(__VA_ARGS__)
#define BAR(fmt, ...)  printf(fmt, __VA_ARGS__)

FOO("this works fine");
BAR("this breaks!");

BAR()위 의 사용은 C99 표준에 따라 실제로 다음 과 같이 확장되기 때문에 올바르지 않습니다.

printf("this breaks!",);

후행 쉼표에 유의하십시오.

일부 컴파일러 (예 : Visual Studio 2010)는 후행 쉼표를 자동으로 제거합니다. 다른 컴파일러 (예 : GCC) ##는 다음 __VA_ARGS__과 같이 앞에 배치 를 지원합니다 .

#define BAR(fmt, ...)  printf(fmt, ##__VA_ARGS__)

그러나이 동작을 수행하는 표준 호환 방법이 있습니까? 여러 매크로를 사용하고 있습니까?

현재 ##버전은 (적어도 내 플랫폼에서는) 상당히 잘 지원되는 것처럼 보이지만 실제로 표준 호환 솔루션을 사용하고 싶습니다.

선제 적 : 작은 함수 만 작성할 수 있다는 것을 알고 있습니다. 매크로를 사용 하여이 작업을 수행하려고합니다.

편집 : 다음은 BAR ()을 사용하려는 이유의 예입니다 (단순하지만).

#define BAR(fmt, ...)  printf(fmt "\n", ##__VA_ARGS__)

BAR("here is a log message");
BAR("here is a log message with a param: %d", 42);

fmt항상 큰 따옴표로 묶인 C 문자열 이라고 가정하면 BAR () 로깅 문에 줄 바꿈이 자동으로 추가됩니다 . 개행을 별도의 printf ()로 인쇄하지 않습니다. 로깅이 라인 버퍼링되고 여러 소스에서 비동기 적으로 오는 경우에 유리합니다.


3
왜 사용하는 BAR대신 FOO처음부터?
GManNickG

@GMan : 마지막에 예제를 추가했습니다
jwd

5
@GMan : 마지막 문장을 읽으십시오 (:
jwd


2
@zwol WG14에 제출 된 최신 버전 은 다음과 같 으며 __VA_OPT__키워드를 기반으로하는 새로운 구문을 사용 합니다. 이것은 이미 C ++에 의해 "채택" 되었으므로 C가 적합 할 것으로 기대합니다. (그것은 그것이 C ++ 17로 빠르게 추적되었는지 또는 C ++ 20으로 설정되었는지 여부를 모릅니다)
Leushenko

답변:


66

이 질문에 대한 Richard Hansen의 답변에,##__VA_ARGS__ 설명 된대로 가변 배열 매크로에 전달할 수있는 인수 수에 대해 하드 코딩 된 상한값을 기꺼이 수락하려는 경우 GCC 확장을 사용하지 않을 수 있습니다 . 그러나 그러한 제한을 원하지 않으면 내 지식으로는 C99 지정 전 처리기 기능 만 사용하는 것이 불가능합니다. 언어에 대한 확장명을 사용해야합니다. clang과 icc는이 GCC 확장을 채택했지만 MSVC는 그렇지 않습니다.

2001 년에 나는 문서 N976 에 표준화를위한 GCC 확장 (및 __VA_ARGS__나머지 매개 변수가 아닌 다른 이름을 사용할 수 있도록하는 관련 확장 )을 작성 했지만위원회로부터 아무런 응답도받지 못했다. 나는 누군가 그것을 읽는지조차 모른다. 2016 년에 그것은 N2023 에서 다시 제안 되었고 , 나는 그 제안이 어떻게 우리에게 의견에 알려 질지 알고있는 사람에게 격려합니다.


2
웹에서 해결책을 찾지 못하는 장애와 판단에 대한 대답이 부족하다고 판단하면 당신이 옳은 것 같습니다.
jwd

2
n976은 당신이 언급하는거야? 나는 나머지 검색 C의 실무 그룹문서를 응답을하지만 하나를 발견하지 않습니다. 후속 회의 의제 조차 없었다 . 이 주제에 대한 또 다른 히트는 C99가 비준되기 전 (뒤에 논의되지 않음)부터 n868 의 노르웨이의 의견 # 4 였습니다.
Richard Hansen 2012

4
예, 특히 후반부입니다. 토론이 comp.std.c있었지만 지금은 Google 그룹스에서 찾을 수 없었습니다. 그것은 실제로 실제위원회로부터 아무런 관심을 얻지 못했다.
zwol

1
나는 증거가 없어서 더 이상 생각하기에 적합한 사람이 아닙니다. 나는 GCC의 전 처리기의 절반을 썼지 만 10 년 전이었고, 그때도 인수 계산 기법을 생각한 적이 없었습니다.
zwol

6
이 확장은 gcc뿐만 아니라 clang & intel icc 컴파일러와도 작동합니다.
ACyclic

112

사용할 수있는 인수 계산 기법이 있습니다.

다음은 BAR()jwd의 질문에서 두 번째 예 를 구현하는 표준 호환 방법 중 하나입니다 .

#include <stdio.h>

#define BAR(...) printf(FIRST(__VA_ARGS__) "\n" REST(__VA_ARGS__))

/* expands to the first argument */
#define FIRST(...) FIRST_HELPER(__VA_ARGS__, throwaway)
#define FIRST_HELPER(first, ...) first

/*
 * if there's only one argument, expands to nothing.  if there is more
 * than one argument, expands to a comma followed by everything but
 * the first argument.  only supports up to 9 arguments but can be
 * trivially expanded.
 */
#define REST(...) REST_HELPER(NUM(__VA_ARGS__), __VA_ARGS__)
#define REST_HELPER(qty, ...) REST_HELPER2(qty, __VA_ARGS__)
#define REST_HELPER2(qty, ...) REST_HELPER_##qty(__VA_ARGS__)
#define REST_HELPER_ONE(first)
#define REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__
#define NUM(...) \
    SELECT_10TH(__VA_ARGS__, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway)
#define SELECT_10TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10

int
main(int argc, char *argv[])
{
    BAR("first test");
    BAR("second test: %s", "a string");
    return 0;
}

이 같은 트릭이 사용됩니다 :

설명

전략은 __VA_ARGS__첫 번째 인수와 나머지 인수 (있는 경우)로 분리하는 것입니다. 따라서 첫 번째 인수 다음에 두 번째 인수 앞에 (있는 경우) 항목을 삽입 할 수 있습니다.

FIRST()

이 매크로는 단순히 첫 번째 인수로 확장되어 나머지는 버립니다.

구현은 간단합니다. 이 throwaway인수 FIRST_HELPER()는 두 개 이상의 인수 를 얻도록 보장 하는데, ...하나 이상의 인수가 필요하기 때문에 필요합니다. 하나의 인수로 다음과 같이 확장됩니다.

  1. FIRST(firstarg)
  2. FIRST_HELPER(firstarg, throwaway)
  3. firstarg

둘 이상인 경우 다음과 같이 확장됩니다.

  1. FIRST(firstarg, secondarg, thirdarg)
  2. FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
  3. firstarg

REST()

이 매크로는 첫 번째 인수를 제외하고 모든 인수로 확장됩니다 (두 개 이상의 인수가있는 경우 첫 번째 인수 다음에 쉼표 포함).

이 매크로의 구현은 훨씬 더 복잡합니다. 일반적인 전략은 인수의 수 (하나 이상)를 세고 (하나의 인수 REST_HELPER_ONE()만 제공된 경우) 또는 REST_HELPER_TWOORMORE()(두 개 이상의 인수가 제공된 경우 ) 로 확장하는 것 입니다. REST_HELPER_ONE()첫 번째 이후에는 인수가 없으므로 나머지 인수는 빈 세트입니다. REST_HELPER_TWOORMORE()또한 간단합니다. 쉼표로 확장되고 첫 번째 인수를 제외한 모든 항목이 이어집니다.

인수는 NUM()매크로를 사용하여 계산됩니다 . 이 매크로 ONE는 하나의 인수 만 제공 TWOORMORE하면 2 개에서 9 개의 인수가 제공되면 확장되고 10 개 이상의 인수가 제공되면 분리됩니다 (10 번째 인수로 확장).

NUM()매크로를 사용하는 SELECT_10TH()인수의 수를 결정하는 매크로를. 이름에서 알 수 있듯이 SELECT_10TH()단순히 10 번째 인수로 확장됩니다. 줄임표로 인해 SELECT_10TH()최소 11 개의 인수를 전달해야합니다 (표준에서는 줄임표에 대해 하나 이상의 인수가 있어야한다고 말합니다). 이것이 마지막 인수로 NUM()전달 throwaway되는 이유입니다 (한 개의 인수를 전달하지 않으면 NUM()10 개의 인수 만 전달되어 SELECT_10TH()표준을 위반 함).

하나의 선택 REST_HELPER_ONE()또는 REST_HELPER_TWOORMORE()연결하여 이루어집니다 REST_HELPER_의 확장과 NUM(__VA_ARGS__)에서 REST_HELPER2(). 의 목적 은와 연결하기 전에 완전히 확장 REST_HELPER()되도록하는 NUM(__VA_ARGS__)것입니다 REST_HELPER_.

하나의 인수로 확장하면 다음과 같습니다.

  1. REST(firstarg)
  2. REST_HELPER(NUM(firstarg), firstarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  4. REST_HELPER2(ONE, firstarg)
  5. REST_HELPER_ONE(firstarg)
  6. (빈)

두 개 이상의 인수를 사용한 확장은 다음과 같습니다.

  1. REST(firstarg, secondarg, thirdarg)
  2. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
  4. REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
  5. REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
  6. , secondarg, thirdarg

1
인수가 10 개 이상인 BAR을 호출하면 실패 할 수 있으며, 더 많은 인수로 확장하기는 쉽지만 항상 처리 할 수있는 인수 수의 상한이 있습니다.
Chris Dodd

2
@ChrisDodd : 맞습니다. 불행히도 컴파일러 특정 확장에 의존하지 않고 인수 수의 제한을 피할 수있는 방법은 없습니다. 또한 인수가 너무 많은지 확실하게 테스트 할 수있는 방법을 모릅니다 (이상한 오류가 아닌 유용한 컴파일러 오류 메시지가 인쇄 될 수 있음).
Richard Hansen

17

일반적인 해결책은 아니지만 printf의 경우 다음과 같은 줄 바꿈을 추가 할 수 있습니다.

#define BAR_HELPER(fmt, ...) printf(fmt "\n%s", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, "")

형식 문자열에서 참조되지 않은 추가 인수를 무시한다고 생각합니다. 따라서 아마도 다음과 같이 벗어날 수도 있습니다.

#define BAR_HELPER(fmt, ...) printf(fmt "\n", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, 0)

C99가 표준 방법없이 승인되었다고 믿을 수 없습니다. AFAICT 문제는 C ++ 11에도 존재합니다.


이 추가 0의 문제는 vararg 함수를 호출하면 실제로 코드에서 끝납니다. Richard Hansen이 제공 한 솔루션 확인
Pavel P

@Pavel은 두 번째 예제에서는 정확하지만 첫 번째 예제는 훌륭합니다. +1.
kirbyfan64sos 2014

11

Boost.Preprocessor 와 같은 것을 사용 하여이 특정 사례를 처리하는 방법이 있습니다 . BOOST_PP_VARIADIC_SIZE 를 사용 하여 인수 목록의 크기를 확인한 다음 조건부로 다른 매크로로 확장 할 수 있습니다. 이것의 단점 중 하나는 0과 1의 인수를 구별 할 수 없으며 다음을 고려하면 그 이유가 분명해진다는 것입니다.

BOOST_PP_VARIADIC_SIZE()      // expands to 1
BOOST_PP_VARIADIC_SIZE(,)     // expands to 2
BOOST_PP_VARIADIC_SIZE(,,)    // expands to 3
BOOST_PP_VARIADIC_SIZE(a)     // expands to 1
BOOST_PP_VARIADIC_SIZE(a,)    // expands to 2
BOOST_PP_VARIADIC_SIZE(,b)    // expands to 2
BOOST_PP_VARIADIC_SIZE(a,b)   // expands to 2
BOOST_PP_VARIADIC_SIZE(a, ,c) // expands to 3

빈 매크로 인수 목록은 실제로 비어있는 하나의 인수로 구성됩니다.

이 경우, 원하는 매크로에는 항상 적어도 하나의 인수가 있으므로 두 개의 "오버로드"매크로로 구현할 수 있습니다.

#define BAR_0(fmt) printf(fmt "\n")
#define BAR_1(fmt, ...) printf(fmt "\n", __VA_ARGS__)

그런 다음 다른 매크로를 사용하여 다음과 같이 전환하십시오.

#define BAR(...) \
    BOOST_PP_CAT(BAR_, BOOST_PP_GREATER(
        BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1))(__VA_ARGS__) \
    /**/

또는

#define BAR(...) BOOST_PP_IIF( \
    BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1), \
        BAR_1, BAR_0)(__VA_ARGS__) \
    /**/

더 읽기 쉬운 것을 찾으십시오 (인수의 수에 매크로를 오버로드하는 일반적인 형태를 제공하기 때문에 첫 번째 방법을 선호합니다).

변수 인수 목록에 액세스하고 변경하여 단일 매크로를 사용하여이 작업을 수행 할 수도 있지만 읽기 어렵고이 문제에 매우 구체적입니다.

#define BAR(...) printf( \
    BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \
    BOOST_PP_COMMA_IF( \
        BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1)) \
    BOOST_PP_ARRAY_ENUM(BOOST_PP_ARRAY_POP_FRONT( \
        BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \
    /**/

또한 왜 BOOST_PP_ARRAY_ENUM_TRAILING이 없습니까? 이 솔루션을 훨씬 덜 끔찍하게 만들 것입니다.

편집 : 좋아, 여기 BOOST_PP_ARRAY_ENUM_TRAILING 및 그것을 사용하는 버전이 있습니다 (이제는 내가 가장 좋아하는 솔루션입니다).

#define BOOST_PP_ARRAY_ENUM_TRAILING(array) \
    BOOST_PP_COMMA_IF(BOOST_PP_ARRAY_SIZE(array)) BOOST_PP_ARRAY_ENUM(array) \
    /**/

#define BAR(...) printf( \
    BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \
    BOOST_PP_ARRAY_ENUM_TRAILING(BOOST_PP_ARRAY_POP_FRONT( \
        BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \
    /**/

1
Boost.Preprocessor +1에 대해 배우게되어 반갑습니다. BOOST_PP_VARIADIC_SIZE()내 답변에 문서화 한 것과 동일한 인수 계산 트릭 을 사용하고 동일한 제한이 있습니다 (특정 개수 이상의 인수를 전달하면 중단됩니다).
Richard Hansen

1
네, 귀하의 접근 방식이 Boost에서 사용하는 것과 동일하지만 부스트 솔루션은 매우 잘 유지 관리되며보다 정교한 매크로를 개발할 때 사용할 수있는 다른 유용한 기능이 많이 있습니다. 재귀는 특히 멋지다 (BOOST_PP_ARRAY_ENUM을 사용하는 마지막 접근 방식의 무대 뒤에서 사용됨).
DRayX

1
실제로 c 태그에 적용되는 Boost 답변 ! 만세!
저스틴

6

디버그 인쇄에 사용하는 매우 간단한 매크로 :

#define __DBG_INT(fmt, ...) printf(fmt "%s", __VA_ARGS__);
#define DBG(...) __DBG_INT(__VA_ARGS__, "\n")

int main() {
        DBG("No warning here");
        DBG("and we can add as many arguments as needed. %s", "nice!");
        return 0;
}

DBG에 전달 된 인수 수에 관계없이 c99 경고는 없습니다.

트릭은 __DBG_INT더미 매개 변수를 추가하므로 ...항상 하나 이상의 인수가 있고 c99가 충족됩니다.


5

최근 비슷한 문제가 발생했으며 해결책이 있다고 생각합니다.

핵심 아이디어는 매크로 NUM_ARGS를 작성 하여 가변성 매크로가 제공되는 인수의 수를 세는 방법이 있다는 것 입니다. 당신의 변형을 사용할 수있는 NUM_ARGS빌드로 NUM_ARGS_CEILING2가변 인자 매크로가 1 개 인자 또는 2 또는-이상의 인수가 주어집니다 여부를 알 수있다. 그런 다음 Bar매크로 를 작성하여 인수를 사용 NUM_ARGS_CEILING2하여 CONCAT두 개의 도우미 매크로 중 하나에 인수를 보낼 수 있습니다. 하나는 정확히 1 개의 인수를 예상하고 다른 하나는 1보다 큰 인수를 기대합니다.

여기 매크로 작성이 트릭을 사용하는 예입니다 UNIMPLEMENTED매우 유사하다 BAR:

1 단계:

/** 
 * A variadic macro which counts the number of arguments which it is
 * passed. Or, more precisely, it counts the number of commas which it is
 * passed, plus one.
 *
 * Danger: It can't count higher than 20. If it's given 0 arguments, then it
 * will evaluate to 1, rather than to 0.
 */

#define NUM_ARGS(...)                                                   \
    NUM_ARGS_COUNTER(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13,       \
                     12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)    

#define NUM_ARGS_COUNTER(a1, a2, a3, a4, a5, a6, a7,        \
                         a8, a9, a10, a11, a12, a13,        \
                         a14, a15, a16, a17, a18, a19, a20, \
                         N, ...)                            \
    N

1.5 단계 :

/*
 * A variant of NUM_ARGS that evaluates to 1 if given 1 or 0 args, or
 * evaluates to 2 if given more than 1 arg. Behavior is nasty and undefined if
 * it's given more than 20 args.
 */

#define NUM_ARGS_CEIL2(...)                                           \
    NUM_ARGS_COUNTER(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \
                     2, 2, 2, 2, 2, 2, 2, 1)

2 단계:

#define _UNIMPLEMENTED1(msg)                                        \
    log("My creator has forsaken me. %s:%s:%d." msg, __FILE__,      \
        __func__, __LINE__)

#define _UNIMPLEMENTED2(msg, ...)                                   \
    log("My creator has forsaken me. %s:%s:%d." msg, __FILE__,      \
        __func__, __LINE__, __VA_ARGS__)

3 단계 :

#define UNIMPLEMENTED(...)                                              \
    CONCAT(_UNIMPLEMENTED, NUM_ARGS_CEIL2(__VA_ARGS__))(__VA_ARGS__)

CONCAT가 일반적인 방식으로 구현되는 곳. 빠른 힌트로, 위의 내용이 혼란스러워 보이는 경우 : CONCAT의 목표는 다른 매크로 "호출"로 확장하는 것입니다.

NUM_ARGS 자체는 사용되지 않습니다. 여기에 기본 트릭을 설명하기 위해 포함 시켰습니다. 멋진 처리 방법은 Jens Gustedt의 P99 블로그 를 참조하십시오 .

두 가지 메모 :

  • NUM_ARGS는 처리하는 인수의 수가 제한되어 있습니다. 숫자는 완전히 임의적이지만 광산은 최대 20 개까지만 처리 할 수 ​​있습니다.

  • 그림과 같이 NUM_ARGS에는 0 개의 인수가 주어지면 1을 반환한다는 함정이 있습니다. 요점은 NUM_ARGS가 기술적으로 인수가 아닌 [쉼표 + 1]을 세는 것입니다. 이 특별한 경우에는 실제로 우리에게 유리합니다. _UNIMPLEMENTED1은 빈 토큰을 잘 처리하여 _UNIMPLEMENTED0을 쓰지 않아도됩니다. Gustedt도 그에 대한 해결 방법을 가지고 있지만 사용하지는 않았지만 우리가 여기서하는 일에 효과가 있는지 확실하지 않습니다.


인수 계산 기법을 기르기위한 +1, 정말로 따르기 어려운 -1
Richard Hansen

추가 한 의견은 개선되었지만 여전히 많은 문제가 있습니다. 1. 토론하고 정의 NUM_ARGS하지만 사용하지는 않습니다. 2. 목적은 UNIMPLEMENTED무엇입니까? 3. 문제의 예제 문제를 결코 해결하지 마십시오. 4. 한 번에 한 단계 씩 확장을 진행하면 작동 방식을 설명하고 각 도우미 매크로의 역할을 설명 할 수 있습니다. 5. 0 개의 논쟁을 논의하는 것은 산만하다; OP는 표준 준수에 대해 질문했으며 0 개의 인수는 금지되어 있습니다 (C99 6.10.3p4). 6. 1.5 단계? 왜 2 단계가 아닌가? 7. "단계"는 순차적으로 발생하는 동작을 의미합니다. 이것은 단지 코드입니다.
Richard Hansen

8. 관련 게시물이 아닌 전체 블로그에 연결합니다. 당신이 언급 한 게시물을 찾을 수 없습니다. 9. 마지막 문단은 어색하다 :이 방법 모호하다. 그렇기 때문에 아무도 올바른 솔루션을 게시 한 적이 없습니다. 또한 작동하고 표준을 준수하는 경우 Zack의 답변이 잘못되어야합니다. 10. 정의해야 CONCAT()합니다. 독자가 작동 방식을 알고 있다고 가정하지 마십시오.
Richard Hansen

(이 의견을 공격으로 해석하지 마십시오. 답변을 크게 표명하고 싶지만 이해하기가 쉽지 않으면 편안하지 않습니다. 답변의 명확성을 향상시킬 수 있다면 당신을 찬양하고 광산을 삭제하십시오.)
Richard Hansen

2
나는이 접근법에 대해 전혀 생각해 본 적이 없었으며, GCC의 현재 전 처리기의 거의 절반을 썼다! 즉, 당신과 Richard의 기술 모두 매크로에 대한 인수의 수에 상한을 부과하기 때문에 "이 효과를 얻는 표준 방법은 없습니다"라고 여전히 말하고 있습니다.
zwol

2

이것은 내가 사용하는 단순화 된 버전입니다. 그것은 여기에 다른 답변의 위대한 기술을 기반으로합니다.

#define _SELECT(PREFIX,_5,_4,_3,_2,_1,SUFFIX,...) PREFIX ## _ ## SUFFIX

#define _BAR_1(fmt)      printf(fmt "\n")
#define _BAR_N(fmt, ...) printf(fmt "\n", __VA_ARGS__);
#define BAR(...) _SELECT(_BAR,__VA_ARGS__,N,N,N,N,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
    return 0;
}

그게 다야.

다른 솔루션과 마찬가지로 매크로의 인수 수로 제한됩니다. 더 많은 것을 지원하려면에 매개 변수 _SELECTN인수 를 더 추가하십시오 . 인수 이름은 카운트가 아닌 인수를 카운트 다운하여 카운트 기반 SUFFIX인수가 역순으로 제공 된다는 것을 상기시켜줍니다 .

이 솔루션은 0 개의 인수를 1 개의 인수 인 것처럼 취급합니다. 그래서 BAR()명목상 "작품", 그것은에 확장하므로 _SELECT(_BAR,,N,N,N,N,1)()으로 확장하는 _BAR_1()()로 확장하는 printf("\n").

원하는 경우 _SELECT다른 수의 인수에 다른 매크로를 사용하여 창의력을 발휘할 수 있습니다. 예를 들어, 여기에는 형식 앞에 'level'인수를 사용하는 LOG 매크로가 있습니다. 형식이 없으면 "(메시지 없음)"을 기록하고, 인수가 하나만 있으면 "% s"를 통해 기록하고, 그렇지 않으면 형식 인수를 나머지 인수에 대한 printf 형식 문자열로 취급합니다.

#define _LOG_1(lvl)          printf("[%s] (no message)\n", #lvl)
#define _LOG_2(lvl,fmt)      printf("[%s] %s\n", #lvl, fmt)
#define _LOG_N(lvl,fmt, ...) printf("[%s] " fmt "\n", #lvl, __VA_ARGS__)
#define LOG(...) _SELECT(_LOG,__VA_ARGS__,N,N,N,2,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    LOG(INFO);
    LOG(DEBUG, "here is a log message");
    LOG(WARN, "here is a log message with param: %d", 42);
    return 0;
}
/* outputs:
[INFO] (no message)
[DEBUG] here is a log message
[WARN] here is a log message with param: 42
*/

-pedantic으로 컴파일 할 때 여전히 경고를 트리거합니다.
PSkocik

1

상황 (적어도 1 인자 존재하는, 결코 0), 당신은 정의 할 수 있습니다 BARBAR(...)사용 옌스 Gustedt의를 HAS_COMMA(...) 쉼표를 감지하고 다음에 파견 BAR0(Fmt)이나 BAR1(Fmt,...)따라.

이:

#define HAS_COMMA(...) HAS_COMMA_16__(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0)
#define HAS_COMMA_16__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
#define CAT_(X,Y) X##Y
#define CAT(X,Y) CAT_(X,Y)
#define BAR(.../*All*/) CAT(BAR,HAS_COMMA(__VA_ARGS__))(__VA_ARGS__)
#define BAR0(X) printf(X "\n")
#define BAR1(X,...) printf(X "\n",__VA_ARGS__)


#include <stdio.h>
int main()
{
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
}

-pedantic경고없이 컴파일합니다 .


0

C (gcc) , 762 바이트

#define EMPTYFIRST(x,...) A x (B)
#define A(x) x()
#define B() ,

#define EMPTY(...) C(EMPTYFIRST(__VA_ARGS__) SINGLE(__VA_ARGS__))
#define C(...) D(__VA_ARGS__)
#define D(x,...) __VA_ARGS__

#define SINGLE(...) E(__VA_ARGS__, B)
#define E(x,y,...) C(y(),)

#define NONEMPTY(...) F(EMPTY(__VA_ARGS__) D, B)
#define F(...) G(__VA_ARGS__)
#define G(x,y,...) y()

#define STRINGIFY(...) STRINGIFY2(__VA_ARGS__)
#define STRINGIFY2(...) #__VA_ARGS__

#define BAR(fmt, ...) printf(fmt "\n" NONEMPTY(__VA_ARGS__) __VA_ARGS__)

int main() {
    puts(STRINGIFY(NONEMPTY()));
    puts(STRINGIFY(NONEMPTY(1)));
    puts(STRINGIFY(NONEMPTY(,2)));
    puts(STRINGIFY(NONEMPTY(1,2)));

    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
}

온라인으로 사용해보십시오!

가정 :

  • 인수가 쉼표 또는 대괄호를 포함하지 않습니다
  • arg를 포함하지 않는 A~ G(hard_collide로 이름을 바꿀 수 있음)

no arg contain comma제한은 좀 더 패스 후 멀티를 선택하여 우회 될 수 있지만 no bracket여전히
l4m2

-2

표준 솔루션은 FOO대신에 사용하는 것입니다 BAR. 몇 가지 이상한 인수 순서 변경이있을 수 있습니다 (아마도 누군가가 __VA_ARGS__그것에있는 인수의 수에 따라 조건부 로 분해하고 재 조립하기 위해 영리한 핵을 만들 수는 있지만) 일반적으로 FOO"보통" 그냥 작동합니다.


1
문제는 "이 동작을 수행하는 표준을 준수하는 방법이 있습니까?"였습니다.
Marsh Ray

2
그리고 질문에는 지금 FOO를 사용하지 않는 근거가 포함되어 있습니다.
Pavel Šimerda 8
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.