雑記帳

トークン連結演算子 (token-pasting operator)

トークン連結演算子 (token-pasting operator)
構文
a ## b
注意点
トークン連結演算子は「C言語それ自体の演算子」ではなく「Cプリプロセッサが解釈する演算子」であるため、「#define ディレクティブ内でのみ使用可能」わかりやすく言い換えると「マクロ置換を定義する式の中だけでしか使用できない」ということである。
言い方を変えれば、「Cプリプロセッサの処理を終えたコードの中に演算子 ## が用いられた式が残ってしまうようなコードはコンパイル時にエラーを吐く」ということになる。
例えば、以下の「コード1」と「コード2」を比べてみてほしい。
■ コード1
#include <stdio.h>

#define TOKEN_PASTING(a,b) a ## b

int main(int argc, char** argv) {

    printf("%d\n", TOKEN_PASTING(1,1));
    return 0;

}
■ コード2
#include <stdio.h>

int main(int argc, char** argv) {

    printf("%d\n", (1 ## 1));
    return 0;

}
「コード1」のように ## という演算子をしっかりとマクロ置換の定義の中で用いている場合、Cプリプロセッサの処理を終えた後の main 関数は
int main(int argc, char** argv) {

    printf("%d\n", 11);
    return 0;

}
という記述に書き変わる。
見ての通り、何の問題もないコードである。
一方で「コード2」の場合、Cプリプロセッサの処理を終えた後の main 関数は
int main(int argc, char** argv) {

    printf("%d\n", (1 ## 1));
    return 0;

}
となるのだが、Cプリプロセッサが処理を担当する演算子 ## がコードの前処理を終えてもなお、依然としてコードの中に残ってしまうため正しくコンパイルが通らない。
このように「#define ディレクティブの外側でトークン連結演算子を直接用いる」つまり「+&& のようなC言語の通常の演算子と同じノリで使用する」ということを試みるとこういった状況に陥るので、その点は注意する必要がある。
要約すれば、
  • 演算子 ## を使いたければ、一旦 #define を噛ませて、その演算子をCプリプロセッサの処理の範疇に置こう
ということである。
意味合い
Cプリプロセッサで解釈される #define ディレクティブ内でのみ使用可能な、字句解析時に ab をそれぞれ単独のトークンとして解釈されないように、ab をソースコード上のテキストレベル連結させ、1つのトークンとして解釈されるようにする演算子 (つまり、スペースを挟まずにそれらを表記上の文字列として連結する演算子) という認識でいれば、基本的に解釈に困る場面に遭遇することは滅多に無いと思われる。(C を専門で学んできたわけではないので断定まではできないが...)
先ほどの説明がどういうことなのかを理解する手助けになりそうな例として以下のコードを考える。
#include <stdio.h>

#define TOKEN_PASTING(a,b) a ## b

int main(int argc, char** argv) {

    printf("TOKEN_PASTING(1,1):\t%d\n", TOKEN_PASTING(1, 1));
    printf("TOKEN_PASTING(1,+1):\t%d\n", TOKEN_PASTING(1, +1));

    printf("TOKEN_PASTING(1,1)+1:\t%d\n", TOKEN_PASTING(1, 1) + 1);
    printf("TOKEN_PASTING(1,+1)+1:\t%d\n", TOKEN_PASTING(1, +1) + 1);

    return 0;

}
まず結果から示すと、上のコードは以下を出力する。
TOKEN_PASTING(1,1):     11
TOKEN_PASTING(1,+1):    2
TOKEN_PASTING(1,1)+1:   12
TOKEN_PASTING(1,+1)+1:  3
これについて何故こうなるのかを補足していく。
コードを見ての通り、トークンを連結するマクロ TOKEN_PASTING
#define TOKEN_PASTING(a,b) a ## b
として定義しているわけだが、まず ab が以下
  • a: 1
  • b: 1
で与えられたとすると、11 をソースコード上のテキストレベルで連結することになるので 11 というテキストへと置換される。
続いてもし、そこに 1 を足した式 11+1 を評価した結果を printf で表示した場合、もちろん 12 という結果が得られることを期待するわけだが、これがちょうど出力3行目の 12 に対応している。
一方で、
  • a: 1
  • b: +1
として与えられた場合、1+1 がテキストレベルで連結されるということで 1+1 というテキストに置換されるが、もちろん字句解析時に 1+1 全体が強制的に一つのトークンとして解釈されるわけでもなく、普通に 1+1 が各々一つのトークンとして解釈される。
余談
あくまで「解釈されるようにする」であって、無理やり1つのトークンとして解釈させる意味を持っているわけではないという解釈で良さそう。
結局は「表記 (字句解析する前のテキスト)」をメタに直接弄っているだけなので、そこまでの大層なことはできない?
因みに、「字句解析ってなんだ?」という場合は、一度「Lex & Yacc」で遊んでみるのが手っ取り早いかもしれない。
  • Lex (Wikipedia 記事)
  • Yacc (Wikipedia 記事)
※ C言語を深く知らない人によるメモなので、専門の人からするとガバガバな認識の可能性あり。
主な使用用途
プレフィックスやサフィックスを付け足しが、恐らくこの演算子の主な使用用途になるのかな?
実際、UNICODE識別子の定義・未定義に応じてLプレフィックスを文字リテラルや文字列リテラルに適宜付け足してくれる「TEXTマクロ(_Tマクロ)」に使われている
↓ (「#ifdef _UNICODE」が真の場合) 多分こんな感じ
#define TEXT(x) L##x
※ちなみにこの演算子をうまく使えば、いろいろとカオスなことができるっぽいけど、今の僕にはまだちょっとわからん。