C条件付きプリプロセッサディレクティブで文字列を比較する方法


92

Cでこのようなことをしなければなりません。charを使用した場合にのみ機能しますが、文字列が必要です。これどうやってするの?

#define USER "jack" // jack or queen

#if USER == "jack"
#define USER_VS "queen"
#elif USER == "queen"
#define USER_VS "jack"
#endif

なぜstrcmpだけを使用できないのですか?

@ブライアン:はい、私も質問を読みました:-)。strcmpが存在することを彼が知っていることを確認したかっただけで、この#defineを実行する理由が考えられないため、応答が啓発的である可能性があります。

2
プリプロセッサだけでなく、通常のコードにも同じことが当てはまることに言及したかっただけです。単純な値で十分な場合は、文字列を使用しないでください。文字列には整数や列挙型よりもはるかに多くのオーバーヘッドがあり、それらを比較する以外に何もする必要がない場合、文字列は間違った解決策です。
swestrup 2010

質問に、望ましい動作と実際の動作に関するもう少し多くの情報が含まれていると便利です。
ブレントブラッドバーン2014年

回答:


69

プリプロセッサディレクティブで可変長文字列比較を完全に行う方法はないと思います。ただし、おそらく次のことを行うことができます。

#define USER_JACK 1
#define USER_QUEEN 2

#define USER USER_JACK 

#if USER == USER_JACK
#define USER_VS USER_QUEEN
#elif USER == USER_QUEEN
#define USER_VS USER_JACK
#endif

または、コードを少しリファクタリングして、代わりにCコードを使用することもできます。


3
または#define USER_VS (3 - USER)、この特定のケースでは可能性があります。:)
ジェシーチザム2016年

17

[更新:2018.05.03]

警告:すべてのコンパイラが同じ方法でC ++ 11仕様を実装しているわけではありません。以下のコードは私がテストしたコンパイラで動作しますが、多くのコメント投稿者は別のコンパイラを使用していました。

Shafik Yaghmourの回答からの引用:コンパイル時のC文字列の長さの計算。これは本当にconstexprですか?

定数式はコンパイル時に評価されることが保証されていません。ドラフトC ++標準セクション5.19からの非規範的な引用のみがあります。

[...]> [注:定数式は翻訳中に評価できます。—文末注]

その言葉canは世界にすべての違いをもたらします。

したがって、constexprコンパイラの作成者による仕様の解釈に応じて、この(または任意の)回答に関するYMMVが関係します。

[2016.01.31更新]

文字列の比較を必要とせずに目標を達成することでOPの全体的な側面を回避したため、以前の回答が気に入らなかった人もいるのでcompile time string compare、ここでより詳細な回答を示します。

できません!C98またはC99にはありません。C11でもありません。MACRO操作の量がこれを変更することはありません。

const-expression使用されるの定義は#if文字列を許可しません。

文字を許可するので、文字に制限する場合は、次のように使用できます。

#define JACK 'J'
#define QUEEN 'Q'

#define CHOICE JACK     // or QUEEN, your choice

#if 'J' == CHOICE
#define USER "jack"
#define USER_VS "queen"
#elif 'Q' == CHOICE
#define USER "queen"
#define USER_VS "jack"
#else
#define USER "anonymous1"
#define USER_VS "anonymous2"
#endif

#pragma message "USER    IS " USER
#pragma message "USER_VS IS " USER_VS

あなたはできる!C ++ 11の場合。比較のためにコンパイル時ヘルパー関数を定義する場合。

// compares two strings in compile time constant fashion
constexpr int c_strcmp( char const* lhs, char const* rhs )
{
    return (('\0' == lhs[0]) && ('\0' == rhs[0])) ? 0
        :  (lhs[0] != rhs[0]) ? (lhs[0] - rhs[0])
        : c_strcmp( lhs+1, rhs+1 );
}
// some compilers may require ((int)lhs[0] - (int)rhs[0])

#define JACK "jack"
#define QUEEN "queen"

#define USER JACK       // or QUEEN, your choice

#if 0 == c_strcmp( USER, JACK )
#define USER_VS QUEEN
#elif 0 == c_strcmp( USER, QUEEN )
#define USER_VS JACK
#else
#define USER_VS "unknown"
#endif

#pragma message "USER    IS " USER
#pragma message "USER_VS IS " USER_VS

だから、最終的には、あなたがのために、最終的な文字列値を選択するあなたの目標accomlish方法変更する必要がありますUSERとをUSER_VS

C99ではコンパイル時の文字列比較を行うことはできませんが、文字列のコンパイル時の選択は行うことができます。

本当にコンパイル時のsting比較を行う必要がある場合は、その機能を許可するC ++ 11以降のバリアントに変更する必要があります。

[元の回答が続きます]

試してみてください:

#define jack_VS queen
#define queen_VS jack

#define USER jack          // jack    or queen, your choice
#define USER_VS USER##_VS  // jack_VS or queen_VS

// stringify usage: S(USER) or S(USER_VS) when you need the string form.
#define S(U) S_(U)
#define S_(U) #U

更新:ANSIトークンの貼り付けがわかりにくい場合があります。;-D

#マクロの前にシングルを置くと、マクロは裸の値ではなく、その値の文字列に変更されます。

##2つのトークンの間にdoubleを入れると、それらは1つのトークンに連結されます。

したがって、マクロにUSER_VSは、設定方法に応じて、展開jack_VSまたはqueen_VSがありますUSER

文字列化マクロは、S(...)名前のマクロの値を文字列に変換されるように、マクロ間接を使用しています。マクロの名前の代わりに。

したがって、設定方法に応じて、(または)にUSER##_VSなります。jack_VSqueen_VSUSER

後で、stringifyマクロが(この例では)のS(USER_VS)値として使用されると、その値()を文字列に変換する間接ステップに渡されます。USER_VSjack_VSS_(jack_VS)queen"queen"

に設定USERするqueenと、最終結果は文字列になります"jack"

トークンの連結については、https//gcc.gnu.org/onlinedocs/cpp/Concatenation.htmlを参照してください。

トークン文字列の変換については、https//gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringificationを参照してください。

[タイプミスを修正するために2015.02.15を更新しました。]


5
@ JesseChisholm、C ++ 11バージョンを確認しましたか?GCC 4.8.1、4.9.1、5.3.0で動作させることができません。{{#if 0 == c_strmp / * here * /(USER、QUEEN)}}の{{トークン "("}}の前に二項演算子がありません
Dmitriy Elisov 2016

3
@JesseChisholmだから、次のように変更すると、C ++ 11の例をコンパイルできまし#if 0 == c_strcmp( USER, JACK )constexpr int comp1 = c_strcmp( USER, JACK ); #if 0 == comp1
Dmitriy Elisov 2016

4
@JesseChisholm、うーん、まだ運がない。constexpr変数はすべてゼロに等しくなります。#ifこの例は、USERがJACKであるためにのみ機能します。USERがQUEENだった場合、それが言うUSER IS QUEENUSER_VS IS QUEEN
ドミトリElisov

9
この答えのこのc ++ 11の部分は間違っています。constexprプリプロセッサディレクティブから関数を呼び出すことはできません(でも)。
interjay 2018

8
この完全な間違った答えは、それを参照した誰かをすでに誤解させています。プリプロセッサからconstexpr関数を呼び出すことはできません。翻訳段階7.前処理が翻訳フェーズ4で行われるまで、constexprのであってもキーワードとして認識されていません
H・ウォルターズ

9

以下はclangで私のために働いた。シンボリックマクロ値の比較として表示されるものを許可します。#error xxxは、コンパイラが実際に何をするかを確認するためのものです。catの定義を#definecat(a、b)a ## bに置き換えると、問題が発生します。

#define cat(a,...) cat_impl(a, __VA_ARGS__)
#define cat_impl(a,...) a ## __VA_ARGS__

#define xUSER_jack 0
#define xUSER_queen 1
#define USER_VAL cat(xUSER_,USER)

#define USER jack // jack or queen

#if USER_VAL==xUSER_jack
  #error USER=jack
  #define USER_VS "queen"
#elif USER_VAL==xUSER_queen
  #error USER=queen
  #define USER_VS "jack"
#endif

これが邪悪なのか、素晴らしいのか、あるいはその両方なのかはわかりませんが、まさに私が探していたものでした。ありがとうございます。もう1つの便利なトリックは、1から始まるxUSER_マクロを#defineすることです。次に、#elsifリストの最後に#else句を追加して、USERが誤って処理方法がわからないものに設定された場合をキャッチできます。(それ以外の場合、0から番号を付けると、0のケースがキャッチオールになります。これは、未定義のシンボルに対するプリプロセッサのデフォルトの数値だからです。)
スクラメージ2018年

8

文字列の代わりに数値を使用してください。

最後に、定数JACKまたはQUEENを文字列に変換するには、stringize(および/またはtokenize)演算子を使用します。


2

すでに上で述べたように、ISO-C11プリプロセッサは文字列比較をサポートしていませ。ただし、「反対の値」でマクロを割り当てる問題は、「トークンの貼り付け」と「テーブルアクセス」で解決できます。Jesseの単純な連結/文字列化マクロソリューションは、連結の評価(ISO C11に準拠)の前に文字列化が行われるため、gcc5.4.0では失敗します。ただし、修正することができます。

#define P_(user) user ## _VS
#define VS(user) P_ (user)
#define S(U) S_(U)
#define S_(U) #U

#define jack_VS  queen
#define queen_VS jack

S (VS (jack))
S (jack)
S (VS (queen))
S (queen)

#define USER jack          // jack    or queen, your choice
#define USER_VS USER##_VS  // jack_VS or queen_VS
S (USER)
S (USER_VS)

最初の行(macro P_())は1つの間接参照を追加して、次の行(macro VS())が文字列化のに連結を終了できるようします(マクロに2層の間接参照が必要な理由を参照 )。文字列化マクロ(S()およびS_())はJesseからのものです。

OPのif-then-else構造よりも保守がはるかに簡単なテーブル(マクロjack_VSqueen_VS)は、Jesseによるものです。

最後に、次の4行のブロックが関数スタイルのマクロを呼び出します。最後の4行のブロックは、ジェシーの答えからのものです。

コードを格納しfoo.cてプリプロセッサを呼び出すと、次のようになりgcc -nostdinc -E foo.cます。

# 1 "foo.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "foo.c"
# 9 "foo.c"
"queen"
"jack"
"jack"
"queen"



"jack"
"USER_VS"

出力は期待どおりです。最後の行は、文字列化の前にUSER_VSマクロが展開されていないことを示しています。


私は実際にしようとするまで、この作品がうまく、比較条件付きコンパイルを行うには、生成された文字列を:#if (S(USER)=="jack")-使用しているとき、私はプリプロセッサのエラーを取得します"- error: invalid token at start of a preprocessor expression
ysap

1

文字列が(あなたの場合のように)コンパイル時定数である場合、次のトリックを使用できます。

#define USER_JACK strcmp(USER, "jack")
#define USER_QUEEN strcmp(USER, "queen")
#if $USER_JACK == 0
#define USER_VS USER_QUEEN
#elif USER_QUEEN == 0
#define USER_VS USER_JACK
#endif

コンパイラーは事前にstrcmpの結果を通知し、strcmpをその結果に置き換えるため、プリプロセッサー・ディレクティブと比較できる#defineが得られます。コンパイラー間に差異があるかどうか/コンパイラー・オプションへの依存性があるかどうかはわかりませんが、GCC4.7.2では機能しました。

編集:さらに調査すると、これはツールチェーン拡張であり、GCC拡張ではないように見えるので、それを考慮に入れてください...


7
これは確かに標準のCではなく、どのコンパイラでもどのように機能するかわかりません。コンパイラーは、式の結果(インラインの場合は関数呼び出しも)を通知できる場合がありますが、プリプロセッサーは通知できません。使用状況である$プリプロセッサ拡張のいくつかの種類は?
ugoren 2013

3
'#if $ USER_JACK == 0'構文は、少なくともネイティブAndroidコード(JNI)の構築に使用されるGNU C ++では機能するようです...私はこれを知りませんでしたが、非常に便利です。それ!
gregko 2013

6
私はこれをGCC4.9.1で試しましたが、これであなたが思っていることを実行できるとは思いません。コードはコンパイルされますが、期待した結果は得られません。'$'は変数名として扱われます。したがって、プリプロセッサは '$ USER_JACK'変数を探していますが、それを見つけず、デフォルト値の0を指定しています。したがって、strcmpに関係なく常にUSER_VSがUSER_QUEENとして定義されます
Vitali

1

answereパトリックとによってジェシー・チザムは、私は次の操作を行い作っ:

#define QUEEN 'Q'
#define JACK 'J'

#define CHECK_QUEEN(s) (s==QUEEN)
#define CHECK_JACK(s) (s==JACK)

#define USER 'Q'

[... later on in code ...]

#if CHECK_QUEEN(USER)
  compile_queen_func();
#elif CHECK_JACK(USER)
  compile_jack_func();
#elif
#error "unknown user"
#endif

の代わりに #define USER 'Q' #define USER QUEEN 動作するはずですが、テストされていません また、機能し、扱いやすいかもしれません。

編集:@Jean-FrançoisFabreのコメントによると、私は自分の答えを適応させました。


変更(s==QUEEN?1:0)することにより(s==QUEEN)、あなたは結果がすでにブールで、三元を必要としない
ジャン=フランソワ・ファーブル

0
#define USER_IS(c0,c1,c2,c3,c4,c5,c6,c7,c8,c9)\
ch0==c0 && ch1==c1 && ch2==c2 && ch3==c3 && ch4==c4 && ch5==c5 && ch6==c6 && ch7==c7 ;

#define ch0 'j'
#define ch1 'a'
#define ch2 'c'
#define ch3 'k'

#if USER_IS('j','a','c','k',0,0,0,0)
#define USER_VS "queen"
#elif USER_IS('q','u','e','e','n',0,0,0)
#define USER_VS "jack"
#endif

基本的には、自動的に初期化された可変長の静的char配列ではなく、手動で初期化された固定長の静的char配列で、常に終了するnull文字で終了します。


0

USERが引用符で囲まれた文字列として定義されている場合は、これを行うことはできません。

ただし、USERがJACK、QUEEN、Jokerなどの場合は、これ行うことができます。

使用する2つのトリックがあります:

  1. トークンスプライシング。文字を連結するだけで、識別子を別の識別子と組み合わせることができます。これにより#define JACK、何かをしなくてもJACKと比較できます。
  2. 可変個引数マクロ展開。可変数の引数を持つマクロを処理できます。これにより、特定の識別子をさまざまな数のコンマに展開でき、文字列の比較になります。

それでは、まず始めましょう:

#define JACK_QUEEN_OTHER(u) EXPANSION1(ReSeRvEd_, u, 1, 2, 3)

さて、私が書いたJACK_QUEEN_OTHER(USER)場合、USERがJACKの場合、プリプロセッサはそれを次のように変換します。EXPANSION1(ReSeRvEd_, JACK, 1, 2, 3)

ステップ2は連結です:

#define EXPANSION1(a, b, c, d, e) EXPANSION2(a##b, c, d, e)

今にJACK_QUEEN_OTHER(USER)なるEXPANSION2(ReSeRvEd_JACK, 1, 2, 3)

これにより、文字列が一致するかどうかに応じて、いくつかのコンマを追加する機会が与えられます。

#define ReSeRvEd_JACK x,x,x
#define ReSeRvEd_QUEEN x,x

USERがJACKであれば、JACK_QUEEN_OTHER(USER)となりEXPANSION2(x,x,x, 1, 2, 3)

USERはQUEENの場合、JACK_QUEEN_OTHER(USER)となりEXPANSION2(x,x, 1, 2, 3)

ユーザーが他の場合には、JACK_QUEEN_OTHER(USER)なりEXPANSION2(ReSeRvEd_other, 1, 2, 3)

この時点で、重大な問題が発生しました。EXPANSION2マクロの4番目の引数は、渡された元の引数がジャック、クイーン、またはその他のものであるかどうかに応じて、1、2、または3のいずれかです。だから私たちがしなければならないのはそれを選ぶことだけです。長い理由から、最後のステップには2つのマクロが必要です。不要に思えますが、EXPANSION2とEXPANSION3になります。

すべてをまとめると、次の6つのマクロがあります。

#define JACK_QUEEN_OTHER(u) EXPANSION1(ReSeRvEd_, u, 1, 2, 3)
#define EXPANSION1(a, b, c, d, e) EXPANSION2(a##b, c, d, e)
#define EXPANSION2(a, b, c, d, ...) EXPANSION3(a, b, c, d)
#define EXPANSION3(a, b, c, d, ...) d
#define ReSeRvEd_JACK x,x,x
#define ReSeRvEd_QUEEN x,x

そして、あなたはこれらをこのように使うかもしれません:

int main() {
#if JACK_QUEEN_OTHER(USER) == 1
  printf("Hello, Jack!\n");
#endif
#if JACK_QUEEN_OTHER(USER) == 2
  printf("Hello, Queen!\n");
#endif
#if JACK_QUEEN_OTHER(USER) == 3
  printf("Hello, who are you?\n");
#endif
}

必須のゴッドボルトリンク:https://godbolt.org/z/8WGa19


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.