Linuxカーネルのメーリングリストで、引数が整数定数式であり、整数定数式であるかどうかをテストするマクロに関する議論がありました。
Martin Ueckerによって提案された ( glibcのtgmath.h から インスピレーション を取得)を使用しない、特に巧妙なアプローチの1つは次のとおりです。
#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))
このマクロは、引数が整数定数式の場合は値1
の整数定数式に展開され、そうでない場合は0
に展開されます。ただし、許可されるのはsizeof(void)
に依存しています(sizeof(int)
とは異なります)。これは GNU C拡張 です。
組み込みなしで、また言語拡張機能に依存せずに、そのようなマクロを書くことは可能ですか?はいの場合、引数を評価しますか?
上記のマクロの説明については、代わりに以下を参照してください: Linux Kernelの__is_constexprマクロ
?:
式の型は、引数がNULLポインター定数か通常のvoid *
かによって異なりますが、 _Generic
で型を検出するという同じ考え方を使用します。
#define ICE_P(x) _Generic((1? (void *) ((x)*0) : (int *) 0), int*: 1, void*: 0)
Ideoneのデモ_Generic
はC11の追加であるため、C99またはそれ以前のものにこだわっている場合は使用できません。
また、 nullポインター定数の定義 および nullポインター定数が?:
式の型と対話する方法 の標準リンクがあります。
値が0の整数定数式、またはvoid *型にキャストされたそのような式は、nullポインター定数と呼ばれます。
そして
2番目と3番目のオペランドの両方がポインターであるか、1つがNULLポインター定数で、もう1つがポインターである場合、結果の型は、両方のオペランドが参照する型のすべての型修飾子で修飾された型へのポインターです。さらに、両方のオペランドが互換型または互換型の異なる修飾バージョンへのポインターである場合、結果の型は複合型の適切に修飾されたバージョンへのポインターです。 一方のオペランドがNULLポインター定数の場合、結果にはもう一方のオペランドの型が含まれます。それ以外の場合、1つのオペランドはvoidまたは修飾バージョンのvoidへのポインターです。この場合、結果の型は適切に修飾されたバージョンのvoidへのポインターです。
sizeof(void)
が標準ではないという修正はありませんが、次のようなことを行うことでsizeof(void) == sizeof(int)
という可能性を回避できます。
#define ICE_P(x) ( \
sizeof(void) != \
sizeof(*( \
1 ? \
((void*) ((x) * 0L) ) : \
((struct { char v[sizeof(void) * 2]; } *) 1) \
) \
) \
)
私はそれが完全な答えではないことを知っていますが、わずかに近いです...
Edit:どのソリューションがさまざまなコンパイラーで機能するかについて、少し調査しました。 Hedley ;で次の情報をすべてエンコードしました。 HEDLEY_IS_CONSTANT
、HEDLEY_REQUIRE_CONTEXPR
、およびHEDLEY__IS_CONSTEXPR
マクロを参照してください。パブリックドメインであり、ヘッダーが1つなので、プロジェクトに簡単にドロップするか、興味のある部分をコピーすることができます。
user2357112のC11マクロshouldはanyC11コンパイラで動作しますが、 SunCC および PGI は現在壊れているため、ブラックリストに登録する必要があります。また、IARは__STDC_VERSION__
をC++モードで定義しますが、このトリックはC++では機能しません(AFAIK nothing does )。したがって、おそらく__cplusplus
isn定義されていません。 GCC、clang(およびemscriptenのようなclangから派生したコンパイラー)、ICC、IAR、XL C/C++で実際に動作することを確認しました。
それ以外に、一部のコンパイラは、古いモードでも拡張として_Generic
をサポートしています。
__has_feature(c_generic_selections)
で確認します(ただし、-Wc11-extensions
警告を無効にすることもできます)また、int
をvoid*
にキャストすると、コンパイラが警告を発する場合があることに注意してください。これを回避するには、最初にintptr_t
thenにキャストしてvoid*
にキャストします。
#define ICE_P(expr) _Generic((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0)
または、__INTPTR_TYPE__
を定義するコンパイラ(GCCなど)では、intptr_t
の代わりにそれを使用でき、stdint.h
を含める必要はありません。
ここで可能な別の実装は、__builtin_types_compatible_p
の代わりに_Generic
を使用することです。私はそれが機能するコンパイラを認識していませんが、元のマクロは機能しませんが、-Wpointer-arith
警告から抜け出します:
#define IS_CONSTEXPR(expr) \
__builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*)
このバージョンは、3.1に戻るGCC、およびclangやICCなどの≥3.1を示す値に__GNUC__
/__GNUC_MINOR__
を定義するコンパイラで動作するはずです。
sizeof(void)
をサポートするコンパイラはすべて動作しますが、警告(-Wpointer-arith
など)に遭遇する可能性は十分にあります。そうは言っても、doがsizeof(void)
をサポートするAFAICTコンパイラーは常にサポートしているようです。そのため、これらのコンパイラーのanyバージョン動作するはずです:
__clang__
も定義)__builtin_constant_p
ユースケースによっては、__builtin_constant_p
をサポートするコンパイラーで使用することをお勧めします。これは、整数定数式よりも少し一般的(そして曖昧)です。コンパイラーがコンパイル時に値を知っているというだけです。これらのコンパイラはそれをサポートすることが知られています:
マクロを使用して、コンパイル時に値を知っているが実行時に遅い場合にコンパイラーが一定に折りたたむことができるコードパスと、コンパイラーに対してブラックボックスであるが実行時に速いコードパスを選択する場合、 __builtin_constant_p
を使用します。
OTOH、値が本当に標準に従ってICEであることを確認する場合は、__builtin_constant_p
を使用しないでください。例として、expr
がICEの場合はexpr
を返し、そうでない場合は-1を返すマクロを次に示します。
#if defined(ICE_P)
# define REQUIRE_ICE(expr) (ICE_P(expr) ? (expr) : (-1))
#else
# define REQUIRE_ICE(expr) (expr)
#endif
次に、VLAを使用する場合にコンパイラーがエラーを表示する場合、マクロで配列を宣言するときにそれを使用できます。
char foo[REQUIRE_ICE(bar)];
とはいえ、GCCとclangはどちらも-Wvla
警告を実装しており、代わりに使用することもできます。 -Wvla
の利点は、ソースコードの変更を必要としないことです(つまり、char foo[bar];
と書くだけでよい)。欠点は、それほど広くサポートされていないことと、 適合配列パラメーター を使用しても診断がトリガーされることです。そのため、多くの誤検知を避けたい場合は、このマクロが最善の策です。
アイデア歓迎:)