とある Pull Request のレビューをしていて、詳細は省略しますが、以下のような MY_FALLTHROUGH
の定義をするコードを見ました。
#if defined(__has_cpp_attribute) && defined(__cplusplus) && __cplusplus >= 201103L
# if __has_cpp_attribute(fallthrough)
# define MY_FALLTHROUGH [[fallthrough]]
# elif __has_cpp_attribute(gnu::fallthrough)
# define MY_FALLTHROUGH [[gnu::fallthrough]]
# elif __has_cpp_attribute(clang::fallthrough)
# define MY_FALLTHROUGH [[clang::fallthrough]]
# endif
#elif defined(__has_attribute)
# if __has_attribute(fallthrough)
# define MY_FALLTHROUGH __attribute__((fallthrough))
# endif
#endif
#ifndef MY_FALLTHROUGH
# define MY_FALLTHROUGH do { } while (0)
#endif
なるほど、C++11 で使える [[fallthrough]]
属性が使えるならそれを、そうじゃない場合は処理系の拡張構文が使えるなら使う、というものです。
で、最後のところ、どの記法も使えない場合の部分
#ifndef MY_FALLTHROUGH
# define MY_FALLTHROUGH do { } while (0)
#endif
に関して 「なぜ空マクロじゃないのか」という意図の確認をしたところ、以下のような回答を得ました。
そこまで強いこだわりはないのですが、
意図としては「セミコロンをつけることを強制できる」です。
[[fallthrough]]
もセミコロンが必要なんですけど忘れがちなんですよね。
なるほど、空マクロにしてしまうと式の終わりのセミコロンをつけずにいてもビルドが通ってしまい、将来環境が変わって有効な定義付きのマクロになった瞬間にビルドエラーになる、という時限爆弾になり得ます。
結局のところ、これは「NOP命令を表現したい」ということでしょう。
アセンブラを知っている方には言わずもがなでしょうが、そうでない人のために書いておくと NOP つまり No Operation = 何もしない命令 です。(NOOP と書くこともありますね)
さて、ここで本題。
C/C++ のマクロで NOP を表現する書き方をいくつか考えてみました。(ググったらきっともっと出てきそう)
ルールは以下くらいです。
- 言語規格標準の範囲内の書き方
- 特定のバージョン以降(C++11 以降とか)というのは許容
- 最適化の結果最終的にコード生成されない(=周辺コードに影響を及ぼさない)ことが期待できる
#define NOP_1 do { } while (0) // 上記のやつ( 0 を false と書くバリエーションもありそう)
#define NOP_2 static_cast<void>(0) // 数値はなんでもいいのかも
#define NOP_3 (void)0 // NOP_2 の C言語版
#define NOP_4 static_assert(true, "") // C++11 以降なら
#define NOP_5 [](){}() // 空で名前もついてないラムダ式の呼び出し(C++11 以降)
NOP_5
はもはや呪文ですが