_Static_assertのまねごと
危ないRiSKのブログ うつ期ver.の「_Static_assert とは」という記事に触発され、私も_Static_assertのまねごとをやってみたくなりました。
まず、第二引数のメッセージ文字列を反映することは無理なので、エラーの検出を行う部分の真似だけにとどめることにします。C++のstatic_assertのまねごとは比較的簡単にカタがつくのですが、C99の場合には意外な問題が見えてきました。ひとつは、条件式が定数式でない場合にエラーにできるようにすること、もうひとつは構造体定義の中など、C1Xの本来の_Static_assertが使える文脈で使えるようにすることです。
C++の場合、配列の要素数やsizeof演算子のオペランドには定数式が要求されます。また、テンプレート引数として整数値を使う場合には定数式でなければなりませんのでこれも使うことができます。ところが、C99には可変長配列があるため、配列の要素数やsizeof演算子のオペランドには必ずしも定数式が要求されません。テンプレートはもちろん使えません。ですので、それら以外の定数式が要求される文法に頼らなければなりません。
となってくると、残るのは、静的変数の初期化子、列挙定数の値、caseラベルの値、ビットフィールドのフィールド幅ぐらいしかありません。ここで、構造体定義の中で使うことを考えると、静的変数の初期化子やcaseラベルの値は使うことができません。したがって、残るのは、列挙とビットフィールドだけになります。この2種類を使った方法をそれぞれ考えてみます。
enum { static_assert_expr = (expr) ? 0 : -1 } static_assert_failed[static_assert_expr]
上記が列挙によるものです。そして、
struct { int static_assertion_failed: (expr) ? 1 : -1; } static_assertion_[0]
上記がビットフィールドによるものです。残念ながら、いずれも規格厳密合致にはできませんでした。というのは、ゼロ長配列を使っているからです。しかし、Visual C++やGCCなど、主だった処理系ではこれでも通用するので、実用上はそう大きな問題はないと思います。C++ならtypedefにするところですが、C99では構造体定義の中でtypedefが使えませんのでしかたがありません。
やや難もありますが、これらが、現時点で私が思いつく最良の実装方法です。もっとよい方法があれば教えてください。とくに、ゼロ長配列を使うことなく規格厳密合致にする方法を。


ビットフィールド版にも問題が...
ちょっと寝ぼけていました。
#define JOIN_(a, b) a##b
#define _Static_assert(expr, message) \
struct { int static_assertion_failed: (expr) ? 1 : -1; } \
JOIN(static_assertion_,__LINE__)[0]
上のようにしないと、同一有効範囲で2回以上使えませんね。
後からよく考えてみると...
列挙版はダメですね。手直しすれば、できないわけではありませんが、エラーメッセージが劣化します。
というわけで、ビットフォールド版しかなさそうです。