GCCの## __ VA_ARGS__トリックの標準的な代替手段ですか?


151

C99の可変個引数マクロの空の引数に関するよく知られた 問題があります。

例:

#define FOO(...)       printf(__VA_ARGS__)
#define BAR(fmt, ...)  printf(fmt, __VA_ARGS__)

FOO("this works fine");
BAR("this breaks!");

BAR()C99標準によれば、上記の使用は実際には正しくありません。

printf("this breaks!",);

末尾のコンマに注意してください-機能しません。

一部のコンパイラー(例:Visual Studio 2010)は、その末尾のコンマを静かに削除します。他のコンパイラ(例:GCC)##__VA_ARGS__、の前に置くことをサポートしています。

#define BAR(fmt, ...)  printf(fmt, ##__VA_ARGS__)

しかし、この動作を実現するための標準準拠の方法はありますか?おそらく複数のマクロを使用していますか?

現在、##バージョンはかなりサポートされているようですが(少なくとも私のプラットフォームでは)、実際には標準に準拠したソリューションを使用します。

プリエンプティブ:小さな関数を書くことができることは知っています。マクロを使用してこれを実行しようとしています。

編集:BAR()を使用する理由の例(単純ですが)を次に示します。

#define BAR(fmt, ...)  printf(fmt "\n", ##__VA_ARGS__)

BAR("here is a log message");
BAR("here is a log message with a param: %d", 42);

これにより、fmt常にBAR()ロギングステートメントに改行が自動的に追加されます。これは改行を別個のprintf()として出力しません。これは、ロギングがラインバッファリングされ、複数のソースから非同期で送信される場合に有利です。


3
そもそもなぜのBAR代わりに使うのFOOですか?
GManNickG 2011

@GMan:最後に例を追加しました
jwd '28

5
@GMan:最後の文を読んでください(:
jwd


2
@zwol WG14に送信された最新バージョンは次のようになり、__VA_OPT__キーワードに基づいた新しい構文を使用します。これはすでに C ++で「採用」されているので、Cもそれに続くと思います。(それがC ++ 17で高速に追跡されたのか、それともC ++ 20で設定されたのかどうかわからない)
Leushenko

回答:


66

この質問に対するRichard Hansenの回答で,##__VA_ARGS__説明されているように、可変長マクロに渡すことができる引数の数のハードコーディングされた上限を受け入れる場合は、GCCの拡張機能の使用を避けることができます。ただし、そのような制限を設定したくない場合は、私の知る限り、C99で指定されたプリプロセッサー機能のみを使用することはできません。言語の拡張を使用する必要があります。clangとiccはこのGCC拡張を採用していますが、MSVCは採用していません。

2001年に私は標準化のためのGCC拡張(および__VA_ARGS__、rest-parameter 以外の名前を使用できるようにする関連拡張)をドキュメントN976に書きましたが、委員会からは何の返答もありませんでした。誰かがそれを読んだかどうかさえ知りません。2016年にN2023で再び提案されました。その提案がコメントで私たちに知らせてくれることを知っている人なら誰でもお勧めします。


2
私がウェブで解決策を見つけることができず、ここに答えがないことから判断すると、私はあなたが正しいと思います):
jwd

2
n976はあなたが言及しているものですか?Cワーキンググループの残りのドキュメントで回答を検索しましたが、見つかりませんでした。それはその後の会議の議題すらありませんでした。このトピックに関する他の唯一のヒットは、C99が承認される前に戻ったn868のノルウェーのコメント#4です(これもフォローアップディスカッションはありません)。
Richard Hansen

4
はい、特にその後半です。議論があったかもしれcomp.std.cませんが、Googleグループで今のところ見つけることができませんでした。実際の委員会から注目されることは決してありませんでした(もしそうだとしても、だれもそれについて私に話したことはありません)。
zwol

1
私には証明がないのではないかと思っています。また、もう1つ考えてみるのに適した人物でもありません。私はGCCのプリプロセッサの半分を作成しましたが、それは10年以上前でした。
zwol 2014年

6
この拡張機能は、clangとintel iccコンパイラ、およびgccで動作します。
2015

112

使用できる引数カウントトリックがあります。

BAR()次に、jwdの質問の2番目の例を実装するための、標準に準拠した方法の1つを示します。

#include <stdio.h>

#define BAR(...) printf(FIRST(__VA_ARGS__) "\n" REST(__VA_ARGS__))

/* expands to the first argument */
#define FIRST(...) FIRST_HELPER(__VA_ARGS__, throwaway)
#define FIRST_HELPER(first, ...) first

/*
 * if there's only one argument, expands to nothing.  if there is more
 * than one argument, expands to a comma followed by everything but
 * the first argument.  only supports up to 9 arguments but can be
 * trivially expanded.
 */
#define REST(...) REST_HELPER(NUM(__VA_ARGS__), __VA_ARGS__)
#define REST_HELPER(qty, ...) REST_HELPER2(qty, __VA_ARGS__)
#define REST_HELPER2(qty, ...) REST_HELPER_##qty(__VA_ARGS__)
#define REST_HELPER_ONE(first)
#define REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__
#define NUM(...) \
    SELECT_10TH(__VA_ARGS__, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway)
#define SELECT_10TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10

int
main(int argc, char *argv[])
{
    BAR("first test");
    BAR("second test: %s", "a string");
    return 0;
}

これと同じトリックは、次の目的で使用されます。

説明

戦略は__VA_ARGS__、最初の引数と残りの部分(ある場合)に分離することです。これにより、最初の引数の後、2番目の引数(存在する場合)の前に、内容を挿入できます。

FIRST()

このマクロは単純に最初の引数に展開され、残りは破棄されます。

実装は簡単です。throwaway引数性を保証FIRST_HELPER()ために必要な2つの引数を取得し、...少なくとも1でニーズを。引数を1つ指定すると、次のように展開されます。

  1. FIRST(firstarg)
  2. FIRST_HELPER(firstarg, throwaway)
  3. firstarg

2つ以上の場合、次のように拡張されます。

  1. FIRST(firstarg, secondarg, thirdarg)
  2. FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
  3. firstarg

REST()

このマクロは、最初の引数以外のすべてに展開されます(複数の引数がある場合、最初の引数の後のコンマを含みます)。

このマクロの実装ははるかに複雑です。一般的な戦略は、引数の数(1つまたは複数)を数え、次にREST_HELPER_ONE()(引数が1つだけの場合)またはREST_HELPER_TWOORMORE()(引数が2つ以上の場合)に展開することです。 REST_HELPER_ONE()単に何も展開しない-最初の後に引数がないため、残りの引数は空のセットです。 REST_HELPER_TWOORMORE()また、簡単です。最初の引数を除くすべてが続くコンマに展開されます。

引数はNUM()マクロを使用してカウントされます。このマクロは、ONE引数が1つだけ指定された場合、2〜9個の引数が指定された場合に展開され、TWOORMORE10個以上の引数が指定された場合はブレークします(10番目の引数に展開されるため)。

NUM()マクロを使用しSELECT_10TH()引数の数を決定するために、マクロ。その名前が示すように、SELECT_10TH()単純に10番目の引数に展開されます。省略記号のため、SELECT_10TH()少なくとも11個の引数を渡す必要があります(標準では、省略記号には少なくとも1つの引数が必要であると規定されています)。これが最後の引数としてNUM()渡さthrowawayれる理由です(引数なしで1つの引数をNUM()渡すとSELECT_10TH()、10のみの引数がに渡され、標準に違反します)。

REST_HELPER_ONE()またはの選択は、in の展開とREST_HELPER_TWOORMORE()連結REST_HELPER_して行われます。の目的は、と連結する前にが完全に展開されるようにすることです。NUM(__VA_ARGS__)REST_HELPER2()REST_HELPER()NUM(__VA_ARGS__)REST_HELPER_

1つの引数を持つ展開は次のようになります。

  1. REST(firstarg)
  2. REST_HELPER(NUM(firstarg), firstarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  4. REST_HELPER2(ONE, firstarg)
  5. REST_HELPER_ONE(firstarg)
  6. (空の)

2つ以上の引数を持つ展開は次のようになります。

  1. REST(firstarg, secondarg, thirdarg)
  2. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
  4. REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
  5. REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
  6. , secondarg, thirdarg

1
これは、10個以上の引数でBARを呼び出すと失敗し、より多くの引数に拡張するのは比較的簡単ですが、処理できる引数の数には常に上限があります
Chris Dodd

2
@ChrisDodd:正解です。残念ながら、コンパイラ固有の拡張機能に依存せずに引数の数の制限を回避する方法はないようです。また、引数が多すぎるかどうかを確実にテストする方法を知りません(奇妙なエラーではなく、有用なコンパイラエラーメッセージが出力されるようにするためです)。
Richard Hansen

17

一般的な解決策ではありませんが、printfの場合、次のような改行を追加できます。

#define BAR_HELPER(fmt, ...) printf(fmt "\n%s", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, "")

フォーマット文字列で参照されていない余分な引数はすべて無視すると思います。したがって、おそらく次のようにして回避することもできます。

#define BAR_HELPER(fmt, ...) printf(fmt "\n", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, 0)

これを行うための標準的な方法がなければ、C99が承認されたとは思えません。AFAICT問題はC ++ 11にも存在します。


この余分な0の問題は、vararg関数を呼び出すと、コードで実際に終了することです。Richard Hansenによって提供された解決策を確認してください
Pavel P

@Pavelは2番目の例については正しいですが、最初の例はうまくいきます。+1。
kirbyfan64sos 14

11

Boost.Preprocessorのようなものを使用して、この特定のケースを処理する方法があります。BOOST_PP_VARIADIC_SIZEを使用して引数リストのサイズを確認し、条件付きで別のマクロに展開できます。これの1つの欠点は、0と1の引数を区別できないことです。この理由は、以下を検討すると明らかになります。

BOOST_PP_VARIADIC_SIZE()      // expands to 1
BOOST_PP_VARIADIC_SIZE(,)     // expands to 2
BOOST_PP_VARIADIC_SIZE(,,)    // expands to 3
BOOST_PP_VARIADIC_SIZE(a)     // expands to 1
BOOST_PP_VARIADIC_SIZE(a,)    // expands to 2
BOOST_PP_VARIADIC_SIZE(,b)    // expands to 2
BOOST_PP_VARIADIC_SIZE(a,b)   // expands to 2
BOOST_PP_VARIADIC_SIZE(a, ,c) // expands to 3

空のマクロ引数リストは、実際には空の1つの引数で構成されています。

この場合、希望のマクロには常に少なくとも1つの引数があるため、幸運です。2つの「オーバーロード」マクロとして実装できます。

#define BAR_0(fmt) printf(fmt "\n")
#define BAR_1(fmt, ...) printf(fmt "\n", __VA_ARGS__)

そして、それらを切り替える別のマクロ、たとえば:

#define BAR(...) \
    BOOST_PP_CAT(BAR_, BOOST_PP_GREATER(
        BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1))(__VA_ARGS__) \
    /**/

または

#define BAR(...) BOOST_PP_IIF( \
    BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1), \
        BAR_1, BAR_0)(__VA_ARGS__) \
    /**/

どちらがより読みやすいと思いますか(引数の数でマクロをオーバーロードするための一般的な形式を提供するので、最初の方を好みます)。

変数の引数リストにアクセスして変更することにより、単一のマクロでこれを行うこともできますが、これは読みにくく、この問題に非常に特有です。

#define BAR(...) printf( \
    BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \
    BOOST_PP_COMMA_IF( \
        BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1)) \
    BOOST_PP_ARRAY_ENUM(BOOST_PP_ARRAY_POP_FRONT( \
        BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \
    /**/

また、なぜBOOST_PP_ARRAY_ENUM_TRAILINGがないのですか?それはこの解決策をはるかに恐ろしくなくするでしょう。

編集:申し分なく、ここにBOOST_PP_ARRAY_ENUM_TRAILINGとそれを使用するバージョンがあります(これは今私のお気に入りのソリューションです):

#define BOOST_PP_ARRAY_ENUM_TRAILING(array) \
    BOOST_PP_COMMA_IF(BOOST_PP_ARRAY_SIZE(array)) BOOST_PP_ARRAY_ENUM(array) \
    /**/

#define BAR(...) printf( \
    BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \
    BOOST_PP_ARRAY_ENUM_TRAILING(BOOST_PP_ARRAY_POP_FRONT( \
        BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \
    /**/

1
Boost.Preprocessor、+ 1について学習できてうれしいです。注BOOST_PP_VARIADIC_SIZE()私は私の答えで文書と同じ引数カウントトリックを使用し、(あなたがより多くの引数の特定の数よりも合格した場合、それが破損します)と同じ制限があります。
Richard Hansen

1
ええ、私はあなたのアプローチがBoostによって使用されたものと同じだったのを見ましたが、boostソリューションは非常によく維持されており、より洗練されたマクロを開発するときに使用する他の本当に便利な機能がたくさんあります。再帰的なものは特にクールです(そして、BOOST_PP_ARRAY_ENUMを使用する最後のアプローチの舞台裏で使用されます)。
DRayX 2013

1
実際にcタグに適用されるBoost回答!やったー!
ジャスティン

6

デバッグ出力に使用している非常に単純なマクロ:

#define __DBG_INT(fmt, ...) printf(fmt "%s", __VA_ARGS__);
#define DBG(...) __DBG_INT(__VA_ARGS__, "\n")

int main() {
        DBG("No warning here");
        DBG("and we can add as many arguments as needed. %s", "nice!");
        return 0;
}

DBGに渡される引数の数に関係なく、c99警告はありません。

トリックは__DBG_INTダミーのパラメーターを追加することですので、...常に少なくとも1つの引数があり、c99は満たされます。


5

最近同様の問題に遭遇しましたが、解決策はあると思います。

重要なアイデアはNUM_ARGS、可変個のマクロが与えられる引数の数を数えるマクロを書く方法があるということです。あなたはのバリエーションを使用することができますNUM_ARGSビルドにNUM_ARGS_CEILING2可変引数マクロは1つの引数または2 -または-複数の引数を与えているかどうかを伝えることができ、。次に、Barマクロを記述して、その引数を使用NUM_ARGS_CEILING2CONCATて2つのヘルパーマクロの1つに送信することができます。

UNIMPLEMENTEDこれは、このトリックを使用してマクロを記述する例です。これは、に非常に似ていBARます。

ステップ1:

/** 
 * A variadic macro which counts the number of arguments which it is
 * passed. Or, more precisely, it counts the number of commas which it is
 * passed, plus one.
 *
 * Danger: It can't count higher than 20. If it's given 0 arguments, then it
 * will evaluate to 1, rather than to 0.
 */

#define NUM_ARGS(...)                                                   \
    NUM_ARGS_COUNTER(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13,       \
                     12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)    

#define NUM_ARGS_COUNTER(a1, a2, a3, a4, a5, a6, a7,        \
                         a8, a9, a10, a11, a12, a13,        \
                         a14, a15, a16, a17, a18, a19, a20, \
                         N, ...)                            \
    N

ステップ1.5:

/*
 * A variant of NUM_ARGS that evaluates to 1 if given 1 or 0 args, or
 * evaluates to 2 if given more than 1 arg. Behavior is nasty and undefined if
 * it's given more than 20 args.
 */

#define NUM_ARGS_CEIL2(...)                                           \
    NUM_ARGS_COUNTER(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \
                     2, 2, 2, 2, 2, 2, 2, 1)

ステップ2:

#define _UNIMPLEMENTED1(msg)                                        \
    log("My creator has forsaken me. %s:%s:%d." msg, __FILE__,      \
        __func__, __LINE__)

#define _UNIMPLEMENTED2(msg, ...)                                   \
    log("My creator has forsaken me. %s:%s:%d." msg, __FILE__,      \
        __func__, __LINE__, __VA_ARGS__)

ステップ3:

#define UNIMPLEMENTED(...)                                              \
    CONCAT(_UNIMPLEMENTED, NUM_ARGS_CEIL2(__VA_ARGS__))(__VA_ARGS__)

CONCATが通常の方法で実装されている場合。簡単なヒントとして、上記が混乱していると思われる場合:CONCATの目的は、別のマクロ「呼び出し」に拡張することです。

NUM_ARGS自体は使用されないことに注意してください。ここでは、基本的なトリックを説明するために含めました。Jens GustedtのP99ブログを参照してください。

2つのメモ:

  • NUM_ARGSは、処理する引数の数に制限があります。鉱山は20までしか処理できませんが、その数は完全に任意です。

  • NUM_ARGSは、示されているように、引数が0の場合に1を返すという落とし穴があります。その要点は、NUM_ARGSが技術的に[カンマ+ 1]を数えているのであって、引数を数えていないことです。この特定のケースでは、それは実際に私たちに有利に働きます。_UNIMPLEMENTED1は空のトークンを問題なく処理し、_UNIMPLEMENTED0を記述する必要をなくします。Gustedtにもそれに対する回避策がありますが、私はそれを使用していません。


+1は引数のカウントトリック
リチャードハンセン

追加したコメントは改善されましたが、まだいくつかの問題があります。1.議論して定義しますNUM_ARGSが、使用しません。2.の目的はUNIMPLEMENTED何ですか?3.質問の例の問題を解決することはありません。4.一度に1ステップずつ拡張を歩くと、拡張がどのように機能するかを示し、各ヘルパーマクロの役割を説明します。5. 0の議論について議論することは邪魔になる; OPは標準への準拠について尋ねていたため、0引数は禁止されています(C99 6.10.3p4)。6.ステップ1.5?なぜステップ2ではないのですか?7.「ステップ」とは、順次発生するアクションを意味します。これは単なるコードです。
リチャードハンセン

8.関連する投稿ではなく、ブログ全体にリンクします。あなたが参照していた投稿が見つかりませんでした。9.最後の段落は扱いにくいです。この方法あいまいです。そのため、これまで他の誰も正しい解決策を投稿していませんでした。また、それが機能し、標準に準拠している場合、ザックの答えは間違っているはずです。10.定義する必要があります。CONCAT()読者がそれがどのように機能するかを知っているとは限りません。
Richard Hansen

(このフィードバックを攻撃と解釈しないでください。私は本当にあなたの回答に賛成票を投じたいと思っていましたが、理解しやすくしない限り、そうすることに抵抗を感じていました。回答の明確性を向上させることができれば、私はあなたのものを賛成して私のものを削除してください。)
リチャード・ハンセン

2
私はこのアプローチを考えたことはなかったでしょうし、GCCの現在のプリプロセッサの約半分を書きました!そうは言っても、あなたとリチャードの両方のテクニックがマクロへの引数の数に上限を課しているので、「この効果を得る標準的な方法はありません」と私はまだ言います。
zwol 2013年

2

これは私が使用する簡易バージョンです。これは、ここでの他の回答の優れたテクニックに基づいているため、それらに対する多くの小道具です。

#define _SELECT(PREFIX,_5,_4,_3,_2,_1,SUFFIX,...) PREFIX ## _ ## SUFFIX

#define _BAR_1(fmt)      printf(fmt "\n")
#define _BAR_N(fmt, ...) printf(fmt "\n", __VA_ARGS__);
#define BAR(...) _SELECT(_BAR,__VA_ARGS__,N,N,N,N,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
    return 0;
}

それでおしまい。

他のソリューションと同様に、これはマクロの引数の数に制限されます。さらにサポートするには、にパラメータを追加し_SELECTN引数を追加します。引数名は、カウントベースのSUFFIX引数が逆の順序で提供されていることを思い出させるために(上ではなく)カウントダウンします。

このソリューションでは、0個の引数を1個の引数のように扱います。したがって、BAR()名目上は「機能」します。これは_SELECT(_BAR,,N,N,N,N,1)()、に展開され_BAR_1()()、に展開され、に展開されるためprintf("\n")です。

必要に応じて、_SELECTさまざまな引数を使用してさまざまなマクロを提供し、クリエイティブを作成できます。たとえば、ここでは、フォーマットの前に「レベル」引数を取るLOGマクロがあります。formatがない場合は「(メッセージなし)」をログに記録し、引数が1つしかない場合は「%s」を介してログに記録します。それ以外の場合は、フォーマット引数を残りの引数のprintfフォーマット文字列として扱います。

#define _LOG_1(lvl)          printf("[%s] (no message)\n", #lvl)
#define _LOG_2(lvl,fmt)      printf("[%s] %s\n", #lvl, fmt)
#define _LOG_N(lvl,fmt, ...) printf("[%s] " fmt "\n", #lvl, __VA_ARGS__)
#define LOG(...) _SELECT(_LOG,__VA_ARGS__,N,N,N,2,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    LOG(INFO);
    LOG(DEBUG, "here is a log message");
    LOG(WARN, "here is a log message with param: %d", 42);
    return 0;
}
/* outputs:
[INFO] (no message)
[DEBUG] here is a log message
[WARN] here is a log message with param: 42
*/

これは、-pedanticを指定してコンパイルした場合でも警告をトリガーします。
PSkocik 2018

1

あなたの状況(少なくとも1つの引数が存在し、0になることはありません)では、BARとして定義しBAR(...)Jens Gustedtを 使用しHAS_COMMA(...)てコンマを検出してから、BAR0(Fmt)またはBAR1(Fmt,...)それに応じてディスパッチできます。

この:

#define HAS_COMMA(...) HAS_COMMA_16__(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0)
#define HAS_COMMA_16__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
#define CAT_(X,Y) X##Y
#define CAT(X,Y) CAT_(X,Y)
#define BAR(.../*All*/) CAT(BAR,HAS_COMMA(__VA_ARGS__))(__VA_ARGS__)
#define BAR0(X) printf(X "\n")
#define BAR1(X,...) printf(X "\n",__VA_ARGS__)


#include <stdio.h>
int main()
{
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
}

-pedantic警告なしでコンパイルします。


0

C(gcc)、762バイト

#define EMPTYFIRST(x,...) A x (B)
#define A(x) x()
#define B() ,

#define EMPTY(...) C(EMPTYFIRST(__VA_ARGS__) SINGLE(__VA_ARGS__))
#define C(...) D(__VA_ARGS__)
#define D(x,...) __VA_ARGS__

#define SINGLE(...) E(__VA_ARGS__, B)
#define E(x,y,...) C(y(),)

#define NONEMPTY(...) F(EMPTY(__VA_ARGS__) D, B)
#define F(...) G(__VA_ARGS__)
#define G(x,y,...) y()

#define STRINGIFY(...) STRINGIFY2(__VA_ARGS__)
#define STRINGIFY2(...) #__VA_ARGS__

#define BAR(fmt, ...) printf(fmt "\n" NONEMPTY(__VA_ARGS__) __VA_ARGS__)

int main() {
    puts(STRINGIFY(NONEMPTY()));
    puts(STRINGIFY(NONEMPTY(1)));
    puts(STRINGIFY(NONEMPTY(,2)));
    puts(STRINGIFY(NONEMPTY(1,2)));

    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
}

オンラインでお試しください!

想定:

  • 引数にカンマまたはブラケットが含まれていません
  • 引数にA〜を含まないG(hard_collideに名前を変更できます)

no arg contain commaいくつかのパスの後でマルチをチェックすることでこの制限を回避できますが、no bracketそれでもまだあります
。– l4m2

-2

標準の解決策はのFOO代わりに使用することですBAR。おそらく引数を並べ替えることができない奇妙なケースがいくつかあります(ただし__VA_ARGS__、引数の数に基づいて条件付きで逆アセンブルおよび再アセンブルする巧妙なハックを誰かが思い付く可能性があります!)が、一般的にはFOO「通常」を使用します。うまくいきます。


1
問題は、「この動作を実現するための標準に準拠した方法があるか」ということでした。
Marsh Ray、

2
そして、質問には、FOOを今の年齢で使用しないという根拠が含まれています。
PavelŠimerda2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.