Variadic Macros
매크로에서도 가변 인수를 사용할 수 있습니다.
매크로 매개변수에는 ...
로 가변 인수임을 표시하고 매크로 정의에는
__VA_ARGS__
식별자를 사용하면 됩니다.
방법 1 : ...
와 __VA_ARGS__
를 사용
gcpp
#define FOO(...) __VA_ARGS__
FOO( aa, bb, cc )
@
aa, bb, cc
-------------------------------
$ gcpp
#define eprintf(...) fprintf (stderr, __VA_ARGS__)
eprintf ("%s:%d: ", input_file, lineno)
@
fprintf (stderr, "%s:%d: ", input_file, lineno)
방법 2 : name...
와 name
를 사용
$ gcpp
#define FOO(args...) args
FOO( aa, bb, cc )
@
aa, bb, cc
---------------------------
$ gcpp
#define eprintf(args...) fprintf (stderr, args)
eprintf ("%s:%d: ", input_file, lineno)
@
fprintf (stderr, "%s:%d: ", input_file, lineno)
인수의 개수
foo(a, b)
와 같이 2 개의 매개변수를 갖는 매크로가 있을 경우
매크로 호출 시 전달되는 인수가 매개변수의 개수와 맞지 않으면 오류가 됩니다.
예를 들어 foo(1)
, foo(1,2,3)
는 오류가 됩니다.
하지만 variadic macro 의 경우는 ...
앞부분의 인수 개수만 맞으면 됩니다.
$ gcpp
#define foo(a, b, ...) a b __VA_ARGS__
foo(100, 200, 300)
foo(100, 200) # 인수가 2 개일 경우도 오류가 되지 않는다.
@
100 200 300
100 200
----------------------------------------
$ gcpp
#define foo(a, b, ...) a b __VA_ARGS__
foo(100) # 인수가 1 개일 경우는 오류. (a, b 최소 2 개 이상이어야 한다.)
@
<stdin>:3:8: error: macro "foo" requires 3 arguments, but only 1 given
<stdin>:1: note: macro "foo" defined here
foo
아래 두 번째 foo()
매크로 호출의 경우 인수의 개수는 1
개로 오류가 되지 않습니다.
$ gcpp
#define foo(a) a
foo(100)
foo() # 인수의 값은 empty 이고 개수는 1 개가 된다.
@
100
# empty
, ##__VA_ARGS__
Variadic macros 를 사용할 때 발생하는 한 가지 문제점은
가변 인수 값으로 전달되는 인수가 empty 일 경우입니다.
이럴 경우 __VA_ARGS__
값이 empty 가 되어 앞에 comma 가 남게 되므로
문법상 오류가 발생합니다.
다음과 같이 __VA_ARGS__
값이 존재할 경우는 확장 결과가 오류가 되지 않지만
$ gcpp
#define eprintf(format, ...) fprintf (stderr, format, __VA_ARGS__)
eprintf ("%s:%d: ", input_file, lineno)
@
fprintf (stderr, "%s:%d: ", input_file, lineno)
format 스트링 값에 %s
와 같은 지정자를 사용하지 않아 __VA_ARGS__
값이
empty 가 되면 확장 결과에 comma 가 남게 되어 문법상 오류가 됩니다.
$ gcpp
#define eprintf(format, ...) fprintf (stderr, format, __VA_ARGS__)
eprintf ("error occurred")
@
fprintf (stderr, "error occurred", ) # comma 가 남아 문법상 오류가 된다.
이와 같은 상황을 위해서 특별히 제공하는 기능이 , ##__VA_ARGS__
입니다.
__VA_ARGS__
값이 empty 일 경우 ##__VA_ARGS__
앞에 ,
가 오면 ,
가 제거됩니다.
$ gcpp
#define eprintf(format, ...) fprintf (stderr, format, ##__VA_ARGS__)
eprintf ("error occurred")
@
fprintf (stderr, "error occurred") # comma 가 제거된다.
새로 추가된 기능으로 __VA_OPT__(arg)
매크로가 있는데
이 매크로는 __VA_ARGS__
와 함께 사용되고 __VA_ARGS__
값이 empty 이면
empty 가 반환되고 그렇지 않으면 arg
값이 반환됩니다.
$ gcpp
#define eprintf(format, ...) fprintf (stderr, format __VA_OPT__(,) __VA_ARGS__)
eprintf ("%s:%d: ", input_file, lineno)
@
fprintf (stderr, "%s:%d: " , input_file, lineno)
------------------------------------------------
$ gcpp
#define eprintf(format, ...) fprintf (stderr, format __VA_OPT__(,) __VA_ARGS__)
eprintf ("error occurred")
@
fprintf (stderr, "error occurred" )
매크로에서 반복 기능 구현하기
CPP 에서는 기본적으로 recursion 이 안되므로 다음과 같은 방식으로 반복 기능이 구현됩니다.
// linux/include/linux/syscalls.h 참조
$ gcpp
#define __MAP0(m,...)
#define __MAP1(m,t,a,...) m(t,a)
#define __MAP2(m,t,a,...) m(t,a), __MAP1(m,__VA_ARGS__)
#define __MAP3(m,t,a,...) m(t,a), __MAP2(m,__VA_ARGS__)
#define __MAP4(m,t,a,...) m(t,a), __MAP3(m,__VA_ARGS__)
#define __MAP5(m,t,a,...) m(t,a), __MAP4(m,__VA_ARGS__)
#define __MAP6(m,t,a,...) m(t,a), __MAP5(m,__VA_ARGS__)
#define __MAP(n,...) __MAP##n(__VA_ARGS__)
__MAP(2, FOO, aa, bb, cc, dd) // 2 번 반복
@
FOO(aa,bb), FOO(cc,dd)
-------------------------------------
__MAP(3, FOO, aa, bb, cc, dd, ee, ff) // 3 번 반복
FOO(aa,bb), FOO(cc,dd), FOO(ee,ff)
__MAP(1, FOO, aa, bb, cc, dd, ee, ff) // 1 번 반복
FOO(aa,bb)
__MAP(0, FOO, aa, bb, cc, dd, ee, ff) // 0 번 반복
// empty
__MAP(2, FOO, aa, bb, cc, dd)
1. __MAP(n,...) __MAP##n(__VA_ARGS__) 매크로의 첫 번째 인수는 반복 횟수가 되고
__MAP##n(__VA_ARGS__) 는 __MAP2(FOO, aa, bb, cc, dd ) 로 확장됩니다.
2. __MAP2(FOO, aa, bb, cc, dd ) 는 __MAP2(m,t,a,...) m(t,a), __MAP1(m,__VA_ARGS__)
매크로 정의에 따라 FOO(aa,bb), __MAP1(FOO, cc, dd) 로 확장되고
3. __MAP1(FOO, cc, dd) 는 __MAP1(m,t,a,...) m(t,a) 매크로 정의에 따라 FOO(cc,dd) 로 확장되어
4. 최종 결과는 FOO(aa,bb), FOO(cc,dd) 가 되게 됩니다.
Quiz
다음은 전달된 인수의 개수를 출력해 주는 매크로입니다. 12 개 까지만 셀 수 있는데 어떻게 동작하는 것일까요?
// linux/include/linux/kernel.h 참조
#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _n, X...) _n
#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
1. COUNT_ARGS(X...) 매크로에서 사용된 X... 와 X 는 variadic macro 방법 2 에 해당합니다.
2. 따라서 , ##X 는 , ##__VA_ARGS__ 와 같은것 입니다.
전달된 인수가 empty 일경우 , ##X 부분이 제거되어 , 12, 11 ... 로 시작하게 되고
전달된 인수가 aa, bb, cc 라면 , aa, bb, cc, 12, 11 ... 형태가 됩니다.
3. __COUNT_ARGS 매크로로 인수들이 전부 전달되면 _n 매개변수 위치에서
인수 개수에 해당하는 번호가 매칭되게 됩니다.
4. 따라서 매크로 확장으로 _n 을 사용하면 결과가 인수 개수가 됩니다.
$ gcpp
#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _n, X...) _n
#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
COUNT_ARGS(foo,bar,zoo) // 인수 개수가 3
@
3 # 결과
-------------------------------------------
$ gcpp
#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _n, X...) _n
#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
COUNT_ARGS() // 인수 개수가 0
@
0 # 결과
인자 개수에 따라 다르게 동작하는 매크로 함수 : https://casterian.net/c_c++/macro-args.html
2.
현재 매크로가 정의되어 있는지는 #ifdef
지시자나 defined
연산자를 이용해
체크해볼 수 있는데요.
매크로 값이 empty 인지는 어떻게 체크해볼 수 있을까요?
< 매크로 foo 값이 empty 일 경우 >
$ gcpp
#define JOIN(x, y) x ## y
#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0)
#define CHECK_EMPTY(x) CHECK(JOIN(CHECK_EMPTY_, x))
#define CHECK_EMPTY_ , 1
#define foo // 매크로 값이 empty
CHECK_EMPTY(foo)
@
1 // 결과
--------------------------------------------
1. 매크로 foo 의 값이 empty 이므로 CHECK_EMPTY(foo) ---> CHECK(JOIN(CHECK_EMPTY_,)) 가 된다.
2. 인수에 해당하는 JOIN(CHECK_EMPTY_, ) 매크로가 먼저 확장되면 CHECK_EMPTY_ 가 되고
CHECK_EMPTY_ 매크로가 존재하므로 다시 확장되면 , 1 가 된다.
3. CHECK(...) 매크로에 전달되는 인수 값이 , 1 가 되므로 CHECK_N( , 1, 0 ) 가 된다.
4. CHECK_N 매크로 정의에 따라 두 번째 인수 값을 취하게 되면 결과는 1 이 된다.
< 매크로 foo 값이 존재할 경우 >
$ gcpp
#define JOIN(x, y) x ## y
#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0)
#define CHECK_EMPTY(x) CHECK(JOIN(CHECK_EMPTY_, x))
#define CHECK_EMPTY_ , 1
#define foo 100 // 매크로 값이 100
CHECK_EMPTY(foo)
@
0 // 결과
------------------------------------------
1. 매크로 foo 의 값이 100 이므로 CHECK_EMPTY(foo) ---> CHECK(JOIN(CHECK_EMPTY_,100)) 가 된다.
2. 인수에 해당하는 JOIN(CHECK_EMPTY_, 100) 매크로가 먼저 확장되면 CHECK_EMPTY_100 가 된다.
3. CHECK(...) 매크로에 전달되는 인수 값이 CHECK_EMPTY_100 가 되므로
CHECK_N( CHECK_EMPTY_100, 0 ) 로 확장된다.
4. CHECK_N 매크로 정의에 따라 두 번째 인수 값을 취하게 되면 결과는 0 이 된다.
3.
C++ 언어에서 타입 정보를 조회하고자 할때 다음의
get_type_name()
, get_type_category()
매크로가 유용하게 사용됩니다.
#include <iostream>
template <typename T> constexpr auto type_name()
{
std::string_view name, prefix, suffix;
#ifdef __clang__
name = __PRETTY_FUNCTION__;
prefix = "auto type_name() [T = ";
suffix = "]";
#elif defined(__GNUC__)
name = __PRETTY_FUNCTION__;
prefix = "constexpr auto type_name() [with T = ";
suffix = "]";
#endif
name.remove_prefix(prefix.size());
name.remove_suffix(suffix.size());
return name;
}
#define get_type_name(T) type_name<T>() // type 값을 전달
#define get_type_category(V) type_name<decltype(V)>() // value 값을 전달
template <typename T>
void print_type(auto msg, T&& arg)
{
std::cout << msg << " T : " << get_type_name(T) << '\n';
std::cout << msg << " arg : " << get_type_category(arg) << '\n';
}
int main()
{
int num = 100;
print_type("num", num);
print_type("100", 100);
}
-----------------------------------------------------
$ g++ -std=c++20 test.cpp
$ ./a.out
num T : int& // lvalue num 의 타입 T 의값은 int&
num arg : int& // lvalue num 의 arg 값의 타입은 int&
100 T : int // rvalue 100 의 타입 T 의값은 int
100 arg : int&& // rvalue 100 의 arg 값의 타입은 int&&
그런데 다음과 같이 전달되는 인수에 ,
콤마가 포함될 경우는 2 개의 인수로 인식이 되어
오류가 발생하는데요.
. . .
int main()
{
auto tup = std::make_tuple( 100, "hello", 3.14 );
std::cout << get_type_name(std::tuple_element_t<1, decltype(tup)>) << '\n';
}
---------------------------------------------------
$ g++ test.cpp
t3.cpp:37:69: error: macro "get_type_name" passed 2 arguments, but takes just 1
89 | std::cout << get_type_name(std::tuple_element_t<1, decltype(t1)>) << '\n';
| ^
이때 다음과 같이 variadic macro 를 사용하면 문제를 해결할 수 있습니다.
#define get_type_name(T, ...) type_name<T, ##__VA_ARGS__>()
#define get_type_category(V, ...) type_name<decltype(V, ##__VA_ARGS__)>()