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

그게 다야.

다른 솔루션과 마찬가지로 매크로의 인수 수로 제한됩니다. 더 많은 것을 지원하려면에 매개 변수 _SELECT와 N인수 를 더 추가하십시오 . 인수 이름은 카운트가 아닌 인수를 카운트 다운하여 카운트 기반 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), 당신은 정의 할 수 있습니다 BAR로 BAR(...)사용 옌스 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.