Argument Prescan
Function-like 매크로에서 매크로 확장은 두 번 발생합니다. 첫 번째로 전달된 인수 값은 매크로 body 에서 치환되기 전에 먼저 완전히 확장됩니다. 이것을 argument prescan 이라고 합니다. 그다음 매크로 body 에서 해당 인수가 사용된 자리에서 확장된 값이 치환됩니다. 두 번째는 최종 확장된 결과에 대해서 다시 한번 매크로 확장이 발생하는데 이것을 main scan 이라고 합니다.
아래 예제를 보면 bar(foo)
매크로가 확장될 때 foo
매크로가 존재한다고 해서
bar(a,b)
로 확장된 후에 bar
매크로가 호출되는 것이 아닙니다
( 그러면 인수 개수가 맞지 않아 bar
매크로에서 오류가 나겠죠 ).
또한 bar(foo) --> lose(foo) --> (1 + (foo)) --> (1 + (a,b))
형태로 확장이 되는 것도 아닙니다.
bar(x)
매크로에는foo
하나의 인수로 전달되지만 이때 argument prescan 에의해foo
매크로가 완전히 확장되어a,b
가 됩니다.그다음
x
매개변수가 사용된 자리에서 확장된 값이 치환되어lose(x) ---> lose(a,b)
로 됩니다. ( 이때 만약에x
매개변수에#
또는##
연산자가 사용되면 그대로foo
가 사용됩니다 ).최종 확장된 결과에 대해서 main scan 이 발생하므로
lose(a,b)
매크로가 확장됩니다.
lose(x)
매크로는 하나의 인수만 가지므로 결과적으로 lose 매크로에서 오류가 발생하게 됩니다.
$ gcpp
#define foo a,b
#define bar(x) lose(x)
#define lose(x) (1 + (x))
bar(foo)
@
<stdin>:5:8: error: macro "lose" passed 2 arguments, but takes just 1
---------------------------------------------------------------------
# lose 매크로에서 오류가 나지 않게 하려면 다음과 같이 매개변수에 ( ) 를 추가해 주면 됩니다.
$ gcpp
#define foo a,b
#define bar(x) lose((x)) // lose(x) ---> lose((x)) 로 변경하면
#define lose(x) (1 + (x)) // a,b 두개의 인수가 (a,b) 하나의 인수로 된다.
bar(foo)
@
(1 + ((a,b)))
##
연산자가 매크로의 매개변수 위치에 사용될 경우
다음을 보면 ##
연산자가 lose 매크로의 매개변수 위치에서 사용되고 있는데요.
이럴 경우 확장이 bar(2) ---> lose(foo_2) ---> lose(a,b)
형태로 되는 것이 아닙니다
( 그러면 lose(x)
매크로와 인수 개수가 맞지 않아 오류가 나겠죠 ).
먼저 bar(2)
매크로가 확장이 되면 ##
연산자에 의해 최종 결과가 lose(foo_2)
가 되는데
여기서 main scan 이 일어나면 lose(foo_2)
이 상태에서 다시 매크로가 확장되는 것이지
lose(foo_2) ---> lose(a,b)
가 되는것이 아닙니다.
$ gcpp
#define foo_2 a,b
#define bar(x) lose(foo_ ## x) // lose(foo_2) 상태에서 main scan 이 발생한다.
#define lose(x) (1 + (x)) // lose(x) 매크로에 foo_2 하나의 인수로 전달되고
// 이후 argument prescan 에의해 a,b 로 확장되면
bar(2) // 최종 결과는 (1 + (a,b)) 가 된다.
@
(1 + (a,b))
예제 )
다음 예제는 function-like macros 메뉴에서 한번 소개된 예제인데 JOIN 매크로는 인수로 전달된 type 과 name 을 하나로 합치는 역할을 합니다. 먼저 매크로 인수 부분을 보면 TYPE 매크로가 사용된 것을 볼 수 있습니다. 인수에 사용된 매크로는 치환되기 전에 먼저 완전히 확장이 이루어지므로
TYPE(std::map<int, int>)
---->(std::map<int, int>)
가 되어 type 값으로 사용됩니다.
확장이 완료된 인수 값들을 이용해 JOIN 매크로가 확장되면 결과가
UNPACK (std::map<int, int>) map_var
가 되는데
자세히 살펴보면 UNPACK (std::map<int, int>)
부분이 UNPACK(...)
매크로 정의와
매칭이 됩니다 ( main scan ).
따라서 다시 매크로 확장이 발생하여 최종 결과가
std::map<int, int> map_var;
가 되게 됩니다.
$ gcpp
#define UNPACK(...) __VA_ARGS__
#define TYPE(...) ( __VA_ARGS__ ) // 전달된 인수에 괄호를 추가하는 매크로
#define JOIN( type, name ) UNPACK type name
JOIN( TYPE(std::map<int, int>), map_var );
@
std::map<int, int> map_var;
매크로 확장을 방지하는 방법
다음 FOO 매크로에 foo, bar 인수가 전달되면 ##
연산자에 의해 foobar ()
로 확장이 되고 이후 main scan 에의해 foobar ()
가 확장되어 최종 결과는 100
이 되게 됩니다.
만약에 main 확장을 방지하고 최종 결과가 foobar ()
가 되게 하려면 어떻게 할까요?
gcpp
#define foobar() 100
#define FOO( name1, name2 ) name1 ## name2 ()
FOO( foo, bar )
@
100
이때는 매크로 body 가 empty 인 임의의 매크로를 선언하여
( 여기서는 NOP()
), 매크로 이름과 괄호 사이에 위치시키면 됩니다.
$ gcpp
#define NOP() // body 가 empty 인 매크로
#define foobar() 100
#define FOO( name1, name2 ) name1 ## name2 NOP() () // 이름과 괄호 사이에 위치시킨다.
FOO( foo, bar )
@
foobar () // 매크로 확장이 일어나지 않는다.
위의 예제에서는 NOP()
매크로를 이용해 main 확장을 방지하여 최종 결과가
foobar ()
가 되었는데요.
만약에 최종 결과인 foobar ()
를 다시 확장시키려면 어떻게 할까요?
이때는 EVAL 과 같은 임의의 매크로를 정의하여 최종 결과를 한번더 확장시키면 됩니다.
그러면 최종 결과가 다시 100 이 되게 됩니다.
$ gcpp
#define NOP()
#define EVAL(...) __VA_ARGS__ // main scan 확장을 위한 EVAL 매크로 정의
#define foobar() 100
#define FOO( name1, name2 ) name1 ## name2 NOP() ()
EVAL( FOO( foo, bar ) ) // EVAL 매크로를 이용해 확장
@
100
------------------------------
1. EVAL 매크로의 인수가 되는 FOO(foo, bar) 는 1 개의 인수가 되는데
먼저 argument prescan 에의해 완전히 확장되면 foobar() 가 된다.
2. 확장된 결과는 __VA_ARGS__ 자리에 오게되고 그다음 main scan 이 발생하면
100 으로 확장됩니다.
$ gcpp
#define EVAL(...) x __VA_ARGS__
EVAL( EVAL (EVAL (EVAL ( y ))))
@
x x x x y
#
, ##
연산자는 인수확장이 적용되지 않습니다.
다음은 CONCAT 매크로를 이용해 foobar
를 만들고 QUOTE 매크로를 이용해
double quote 을 하려고 한 것인데요.
그런데 결과를 보면 CONCAT 매크로가 확장이 되지 않습니다.
이것은 QUOTE 매크로에서 사용된 #x
연산자에 의해 인수확장이 적용되지 않기 때문입니다.
$ gcpp
#define CONCAT(x) x ## bar
#define QUOTE(x) #x
QUOTE( CONCAT(foo) )
@
"CONCAT(foo)"
따라서 CONCAT 매크로를 확장 시키기 위해서는 QUOTE 매크로 확장을 방지해야 합니다.
$ gcpp
#define NOP()
#define CONCAT(x) x ## bar
#define QUOTE(x) #x
QUOTE NOP() ( CONCAT(foo) )
@
QUOTE ( foobar )
그런다음 결과에 EVAL 매크로를 적용시키면 CONCAT 과 QUOTE 매크로를 모두 확장시킬 수 있습니다.
$ gcpp
#define NOP()
#define EVAL(...) __VA_ARGS__
#define CONCAT(x) x ## bar
#define QUOTE(x) #x
EVAL( QUOTE NOP() ( CONCAT(foo) ))
@
"foobar"
--------------------------------
1. EVAL 매크로의 인수 부분에 있는 QUOTE NOP() (CONCAT(foo)) 가 먼저
argument prescan 에의해 완전히 확장되어 QUOTE(foobar) 가 된다.
2. 확장된 결과가 EVAL 매크로의 인수 값으로 사용되면 __VA_ARGS__ 자리에
QUOTE(foobar) 가 오게되고 그다음 main scan 이 발생하면 "foobar" 가 됩니다.
또는 QUOTE 매크로에 #
연산자가 사용되어 발생하는 문제이므로
다음과 같이 indirection 을 이용해도 CONCAT(foo)
매크로가 확장됩니다.
$ gcpp
#define CONCAT(x) x ## bar
#define QUOTE_(x) #x
#define QUOTE(x) QUOTE_(x) // '#' 연산자가 없으므로 CONCAT(foo) 확장 결과가 사용된다.
QUOTE ( CONCAT(foo) )
@
"foobar"
---------------------------
1. QUOTE(CONCAT(foo)) 매크로의 인수 부분에 있는 CONCAT(foo) 매크로가
먼저 argument prescan 에의해 완전히 확장되어 foobar 가 된다.
2. QUOTE(x) 매크로의 매개변수 x 의 값이 foobar 로 치환되므로 QUOTE_(foobar) 가 된다.
2. QUOTE_(foobar) 매크로가 확장되면 #x 연산자에 의해 "foobar" 가 된다.
NOP()
매크로를 이용한 확장 방지는 다음과 같이 DEFER(...)
매크로를 정의해
사용할 수 있습니다.
$ gcpp
#define NOP()
#define DEFER(...) __VA_ARGS__ NOP()
#define EVAL(...) __VA_ARGS__
#define CONCAT(x) x ## bar
#define QUOTE(x) #x
DEFER(QUOTE) (CONCAT(foo)) // QUOTE NOP() 대신에 사용할 수 있다.
@
QUOTE (foobar)
Quiz
아래의 COUNT_ARGS(CCC(111,222,333))
매크로 확장 결과로 전달되는 인수의 개수
3
을 기대하였지만 결과로 1
이 출력되고 있습니다.
이것은 COUNT_ARGS(X...)
매크로에서 사용된 ##X
연산자 때문에 CCC(111,222,333)
매크로 확장 결과가 사용되지 않기 때문인데요.
어떻게 하면 CCC(111,222,333)
확장 결과가 전달되어
인수의 개수가 3
이 출력되게 할 수 있을까요?
$ gcpp
#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _n, X...) _n
#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 5, 4, 3, 2, 1, 0)
#define CCC(...) __VA_ARGS__
COUNT_ARGS( CCC(111,222,333) )
@
1
----------------------------------
$ gcpp
#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _n, X...) _n
#define COUNT_ARGS(X...) #X
#define CCC(...) __VA_ARGS__
COUNT_ARGS( CCC(111,222,333) )
@
"CCC(111,222,333)" // 다음과 같이 하나의 인수가 된다.
첫 번째 방법은 indirection 을 이용하는 방법입니다.
다음과 같이 호출 단계를 한 단계 더 추가하면 COUNT_ARGS(X)
매크로에서는 ##
연산자가
사용되지 않기 때문에 CCC(111,222,333)
매크로의 확장 결과가 사용될 수 있습니다.
매개변수 X
의 값이 확장 결과인 111,222,333
로 치환되면
_COUNT_ARGS(111,222,333)
가 되고 main scan 이 발생하면 확장 결과로 3
이되게 됩니다.
$ gcpp
#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _n, X...) _n
#define _COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 5, 4, 3, 2, 1, 0)
#define COUNT_ARGS(X) _COUNT_ARGS(X)
#define CCC(...) __VA_ARGS__
COUNT_ARGS( CCC(111,222,333) )
@
3
두 번째 방법은 다음과 같이 DEFER
와 EVAL
매크로를 이용하는 방법입니다.
$ gcpp
#define NOP()
#define EVAL(...) __VA_ARGS__
#define DEFER(...) __VA_ARGS__ NOP()
#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _n, X...) _n
#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 5, 4, 3, 2, 1, 0)
#define CCC(...) __VA_ARGS__
EVAL( DEFER( COUNT_ARGS ) ( CCC(111,222,333) ) )
@
3
2 .
LISP 언어의 CAR, CDR, CONS 를 매크로로 구현하는 것입니다.
CAR 는 리스트에서 첫 번째 원소를 출력하고
CDR 은 리스트에서 첫 번째 원소를 제외한 나머지 원소들을 출력합니다.
CONS 는 첫 번째 인수로 주어진 원소를 두 번째 인수로 주어진 리스트에 병합합니다.
$ gcpp
#define EVAL(...) __VA_ARGS__
#define JOIN(X, Y) X ## Y
#define CAT(X, Y) JOIN(X, Y) // JOIN 대신 CAT 을 사용하면 인수 확장이 됨.
#define IF_1(true, ...) true
#define IF_0(true, ...) __VA_ARGS__
#define IF(value) CAT(IF_, value)
#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 CAR(list) CAR_ list
#define CDR(list) CDR_ list
#define CAR_(X, ...) X
#define CDR_(X, ...) (__VA_ARGS__)
#define ISEMPTY(list) CHECK_EMPTY(CAR(list))
#define CONS(X, list) IF(ISEMPTY(list)) ((X), (X, EVAL list))
CAR( (33, 44, 55, 66) )
CDR( (33, 44, 55, 66) )
CONS( 22, (33, 44, 55, 66) )
CONS( 22, () )
@
33 # CAR
(44, 55, 66) # CDR
(22, 33, 44, 55, 66) # CONS
(22) # CONS