[C] 인자 개수에 따라 다르게 동작하는 매크로 함수

매크로 함수를 만드는데, 인자 개수에 따라 다른 동작을 하고 싶습니다. 어떻게 하면 좋을까요? 가장 간단한 방법은 인자 개수별로 매크로 함수를 따로 만드는 것입니다.

#define func_1(a) //something...
#define func_2(a, b) //something...
#define func_3(a, b, c) //something...
#define func_4(a, b, c, d) //something...

이 방식은 인자 개수가 몇 개인지 세어서 개수에 맞는 함수를 호출해야만 한다는 문제가 있습니다. 이를 해결하려면 C++의 함수 오버로딩처럼 일종의 ‘제네릭’한 매크로 함수를 만들어야 합니다. 인자 개수에 따라 다르게 확장되는 매크로 함수를 정의하면 가능합니다.

#define VA_GENERIC(_1, _2, _3, _4, x, ...) x

이 함수는 다음과 같이 사용합니다.

#define func(...) \
VA_GENERIC(__VA_ARGS__, func_4, func_3, func_2, func_1)(__VA_ARGS__)

예를 들어 func(1, 2, 3)은 다음과 같이 매크로 확장됩니다.

func(1, 2, 3)
VA_GENERIC(1, 2, 3, func_4, func_3, func_2, func_1)(1, 2, 3)
func_3(1, 2, 3)

보시다시피 인자 개수에 맞춰 정확한 함수를 자동으로 호출합니다. 위 예제는 인자 개수를 최대 4개까지만 지원하는데 VA_GENERIC을 조금만 수정하면 얼마든지 늘릴 수 있습니다.

고급 예제

가변 인자 함수의 인자 개수 자동으로 계산하기

매크로 함수가 아니라 일반적인 가변 인자 함수는 인자 개수를 자동으로 구해주지 않습니다. 첫 번째 인자로 인자 개수를 직접 계산해서 넣어주거나 최소한 첫 번째 인자로부터 총 인자 개수를 구할 수 있어야 합니다. 표준 라이브러리의 printf는 첫 번째 인자로 포맷 문자열을 받아 이를 보고 인자 개수를 계산합니다. 보통은 다음과 같이 인자 개수를 직접 넣어주죠.

int add_int(int argc, ...) {
    va_list ap;
    int sum = 0;

    va_start(ap, argc);
    for (int i = 0; i < argc; i++) {
        int n = va_arg(ap, int);
        sum += n;
    }
    va_end(ap);
}

위의 VA_GENERIC 매크로를 사용하면 첫 번째 인자 argc를 자동으로 구해줄 수 있습니다.

#define NUM_VA_ARGS(...) VA_GENERIC(__VA_ARGS__, 4, 3, 2, 1)
#define ADD_INT(...) add_int(NUM_VA_ARGS(__VA_ARGS__), __VA_ARGS__)

모두 참인지 검사하기

C에서 스칼라(정수, 부동소수점, 포인터) 값은 참/거짓으로 변환하여 논리 연산자의 피연산자로 쓸 수 있습니다. 여러 스칼라 값을 받아서 모두 다 참인지 계산할 수 있을까요? 각 값의 타입이 다를 수 있기 때문에 일반적인 가변 인자 함수를 쓰려면 각 값의 타입을 첫 번재 인자로 넣어 주는 등의 번거로운 작업이 필요하지만, VA_GENERIC을 쓰면 아름답게 해결됩니다.

#define AT_1(a) (a)
#define AT_2(a, ...) ((a) && AT_1(__VA_ARGS__))
#define AT_3(a, ...) ((a) && AT_2(__VA_ARGS__))
#define AT_4(a, ...) ((a) && AT_3(__VA_ARGS__))

#define ALL_TRUE(...) \
VA_GENERIC(__VA_ARGS__, AT_4, AT_3, AT_2, AT_1)(__VA_ARGS__)

ALL_TRUE(-1, 2, 3)          // true
ALL_TRUE(0, -5.7)           // false
ALL_TRUE(123ull, (void *)0) // false