プロプロセッサのマクロ展開と ## 演算子 (1)

Windows API には TCHAR 型というものがある((MSDN ライブラリでは TCHARunsigned chartypedef だと解説されているが、Visual C++ 2008 Express Edition に付いてきた WinNT.h では chartypedef になっていた。)):

#ifdef UNICODE
    typedef wchar_t TCHAR;
#else
    typedef char TCHAR;
#endif

wchar_t 型ではないのでワイド文字としては扱えず、char でもないので標準 C ライブラリなどの各種 API にも渡せないという扱いづらいやつだが、コードページから Unicode への過渡期には大きな役割を果たしてくれた型である。
さて、この TCHAR 型には TEXT() マクロという相方がいて、“TCHAR リテラル”とでも呼ぶべきものを書けるようになっている。すなわち、TEXT("some string") と書くと、マクロ UNICODEdefine されているかに応じて以下のいずれかに展開される:

UNICODE が定義されている
L"some string"
UNICODE が定義されていない
"some string"
面白いマクロなので定義を見てみよう((WinNT.h で define されている。必要なところだけ抜き出してきて整形してある。)):

#ifdef UNICODE
#   define __TEXT(quote) L ## quote
#else
#   define __TEXT(quote) quote
#endif

#define TEXT(quote) __TEXT(quote)

キーになるのは、## という演算子だ。これは、マクロの置換テキストの中でのみ利用可能な演算子で、マクロの実引数を字句レベルで連結するという離れ業をやってのける。これにより、TEXT("some string") は、__TEXT("some string") から L ## "some string" を経て L"some string" に展開される。
ところで、ここに1つの疑問が湧く。なんで、__TEXT() マクロを経由しないといけないのだろう? シンプルに「#define TEXT(quote) L ## quote」ではダメなのだろうか?
結論から言うと、このシンプルな解はダメだ。以下の2つをそれぞれコンパイルしてみれば分かる:

コンパイル可能

#include <windows.h>
#include <tchar.h>

int APIENTRY _tWinMain( HINSTANCE hInstance,
                        HINSTANCE hPrevInstance,
                        LPTSTR    lpCmdLine,
                        int       nCmdShow )
{
    MessageBox(NULL, __TEXT("Hello, world."), TEXT("hello"), MB_OK);
    return 0;
}

コンパイル不能

#include <windows.h>
#include <tchar.h>

#define HELLO_WORLD "Hello, world."

int APIENTRY _tWinMain( HINSTANCE hInstance,
                        HINSTANCE hPrevInstance,
                        LPTSTR    lpCmdLine,
                        int       nCmdShow )
{
    MessageBox(NULL, __TEXT(HELLO_WORLD), TEXT("hello"), MB_OK);
    return 0;
}

両者の違いは、__TEXT() マクロに渡す "Hello, world." という文字列を直接書くか HELLO_WORLD というマクロ経由で渡すかということだけだ。ただそれだけの違いで、コンパイルができたりできなかったりする。そして、__TEXT() ではなく TEXT() を使えば、HELLO_WORLD マクロ経由でもコンパイルできるようになるのだ。
では、これを切り口にしてプリプロセッサのマクロ展開と ## 演算子の面白い挙動を見ていこう、……と思ったが、長くなってしまったので今日はこの辺りまでで。ちなみに、全てを物語るのは、コンパイラからのこのメッセージだ:

(11) : error C2065: 'LHELLO_WORLD' : 定義されていない識別子です。