Cプリプロセッサで2回連結し、「arg ## _ ## MACRO」のようにマクロを展開する方法は?


152

一部の関数の名前が特定のマクロ変数の値に依存しているプログラムを、次のようなマクロで記述しようとしています。

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);

残念ながら、マクロNAME()はそれを

int some_function_VARIABLE(int a);

のではなく

int some_function_3(int a);

ですから、これは明らかに間違った方法です。幸い、VARIABLEの可能な値の数は少ないので、単純にを実行し#if VARIABLE == nてすべてのケースを個別にリストできますが、それを行うための巧妙な方法があるかどうか疑問に思いました。


3
代わりに関数ポインタを使用したくないですか?
ジェルジAndrasek

8
@Jurily-関数ポインタは実行時に機能し、プリプロセッサはコンパイル時(前)に機能します。同じタスクに両方を使用できる場合でも、違いがあります。
クリス・ルッツ

1
ポイントは、それが使用されているのは、特定の次元にハードワイヤードされている高速計算ジオメトリライブラリです。ただし、場合によっては、いくつかの異なる次元(たとえば、2と3)でそれを使用できるようにしたい場合があるため、次元に依存する関数と型の名前でコードを生成する簡単な方法が必要になります。また、コードはANSI Cで記述されているため、テンプレートと特殊化を備えたファンキーなC ++要素はここでは適用されません。
JJ。

2
この質問は再帰的なマクロ展開に固有であり、stackoverflow.com / questions / 216875 / using-in-macrosは一般的な「何に適しているか」という理由で、再開することに投票します。この質問のタイトルをより正確にする必要があります。
Ciro Santilli郝海东冠状病六四事件法轮功

この例が最小化されていれば幸いです。同じことが#define A 0 \n #define M a ## A2つ##あることが重要です。
Ciro Santilli郝海东冠状病六四事件法轮功

回答:


223

標準Cプリプロセッサ

$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y)  PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)

extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"





extern void mine_3(char *x);
$

2レベルの間接参照

別の回答へのコメントで、Cade Roux これに2レベルの間接参照が必要な理由を尋ねました。ばかげた答えは、標準がそれを機能させるために必要な方法だからです。文字列化演算子にも同等のトリックが必要であることがわかります。

C99標準のセクション6.10.3は「マクロ置換」をカバーし、6.10.3.1は「引数置換」をカバーします。

関数のようなマクロの呼び出しの引数が識別された後、引数の置換が行われます。置換リストのパラメーターは、前処理トークン#または##前処理トークンが前に付いていないか、前処理トークンが後に続く場合##(下記参照)を除き、そこに含まれるすべてのマクロが展開された後、対応する引数に置き換えられます。置換される前に、各引数の前処理トークンは、前処理ファイルの残りの部分を形成しているかのように完全にマクロ置換されます。他の前処理トークンは使用できません。

呼び出しNAME(mine)では、引数は「私のもの」です。それは完全に「私のもの」に拡張されます。次に、置換文字列に置き換えられます。

EVALUATOR(mine, VARIABLE)

これで、マクロEVALUATORが見つかり、引数は「mine」と「VARIABLE」として分離されました。後者は完全に「3」に拡張され、置換文字列に置き換えられます。

PASTER(mine, 3)

これの操作は他のルールでカバーされます(6.10.3.3 '##演算子'):

関数のようなマクロの置換リストで、パラメーターの直前または直後に前##処理トークンが続く場合、パラメーターは対応する引数の前処理トークンシーケンスに置き換えられます。[...]

オブジェクトのようなマクロ呼び出しと関数のようなマクロ呼び出しの両方で、置換するマクロ名を置き換えるために置換リストを再検査する##前に、(引数からではなく)置換リスト内の前処理トークンの各インスタンスが削除され、前の前処理トークンが連結されます。次の前処理トークンを使用します。

したがって、置換リストには、がx続いて##、さらにも##続きyます。だから私たちは:

mine ## _ ## 3

##トークンを削除し、どちらかの側のトークンを連結すると、「mine」と「_」および「3」が結合され、次のようになります。

mine_3

これは望ましい結果です。


元の質問を見ると、コードは( 'some_function'ではなく 'mine'を使用するようになっています):

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

NAME(mine)

NAMEへの議論は明らかに「私のもの」であり、それは完全に拡張されています。
6.10.3.3の規則に従って、次のことがわかります。

mine ## _ ## VARIABLE

ときに、##事業者が排除されているが、にマップ:

mine_VARIABLE

質問で報告されたとおりです。


従来のCプリプロセッサ

ロバート・リューガー は尋ねます

トークン貼り付け演算子がない従来のCプリプロセッサでこれを行う方法はあります##か?

多分そうでないかもしれません—それはプリプロセッサに依存します。標準プリプロセッサの利点の1つは、この機能が確実に機能することですが、標準プリプロセッサには異なる実装があったことです。1つの要件は、プリプロセッサがコメントを置き換えるときに、ANSIプリプロセッサが行う必要があるようにスペースを生成しないことです。GCC(6.3.0)Cプリプロセッサはこの要件を満たしています。XCode 8.2.1のClangプリプロセッサはサポートしていません。

それが機能するとき、これは仕事をします(x-paste.c):

#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

fun,との間にスペースがないことに注意してください。存在するVARIABLE場合、それは出力にコピーさmine_ 3れ、名前としてで終わることに注意してください。もちろん、構文的に有効ではありません。(今、髪を戻してもらえますか?)

GCC 6.3.0(実行中cpp -traditional x-paste.c)では、次のようになります。

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);

XCode 8.2.1のClangを使用すると、次のようになります。

# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2





extern void mine _ 3(char *x);

それらのスペースはすべてを台無しにします。どちらのプリプロセッサも正しいことに注意してください。さまざまな先行標準プリプロセッサが両方の動作を示したため、コードの移植時にトークンの貼り付けが非常に煩わしく信頼性の低いプロセスになりました。##表記の標準はそれを根本的に単純化します。

これを行うには他の方法があるかもしれません。ただし、これは機能しません。

#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

GCCが生成するもの:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

近いですがサイコロはありません。もちろん、YMMVは、使用している先行標準プリプロセッサーによって異なります。率直に言って、協調していないプリプロセッサに行き詰まっている場合は、標準より前のCの代わりに標準のCプリプロセッサを使用する方が簡単です(通常、コンパイラを適切に構成する方法があります)。仕事をする方法を考え出すために多くの時間を費やします。


1
はい、これで問題は解決しました。私は2つのレベルの再帰でトリックを知っていました-少なくとも一度は文字列化をしなければなりませんでした-しかし、これを行う方法がわかりませんでした。
JJ。

トークン貼り付け演算子##を持たない従来のCプリプロセッサでこれを行う方法はありますか?
RobertRüger

1
@RobertRüger:回答の長さが2倍になりますが、カバーする情報を追加しましたcpp -traditional。決定的な答えはないことに注意してください—それはあなたが持っているプリプロセッサに依存します。
ジョナサンレフラー

回答ありがとうございます。これはすごい!その間、私は別の少し異なる解決策も見つけました。こちらをご覧ください。また、clangでは動作しないという問題もあります。幸い、それは私のアプリケーションの問題ではありません...
RobertRügerDec

32
#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

正直なところ、これが機能する理由を知りたくありません。なぜそれが機能するのかを知っているなら、あなたはこの種のことを知っている職場でのその人になるでしょう、そして誰もがあなたに質問をしてくるでしょう。=)

編集:なぜそれが機能するのかを実際に知りたい場合は、誰も私に打ち負かされないと仮定して、私は喜んで説明を投稿します。


2レベルの間接参照が必要な理由を説明してください。1レベルのリダイレクトで回答がありましたが、Visual StudioにC ++をインストールする必要があり、機能しなかったため、回答を削除しました。
Cade Roux
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.