条件付きインクルード
プリプロセッサは、ソースファイルの一部を条件付きでコンパイルする機能をサポートしています。この動作は、#if、#else、#elif、#ifdef、#ifndef、#elifdef、#elifndef(C++23以降)、および#endifディレクティブによって制御されます。
目次 |
[編集] 構文
#if 式 |
|||||||||
#ifdef 識別子 |
|||||||||
#ifndef 識別子 |
|||||||||
#elif 式 |
|||||||||
#elifdef 識別子 |
(C++23から) | ||||||||
#elifndef 識別子 |
(C++23から) | ||||||||
#else
|
|||||||||
#endif
|
|||||||||
[編集] 説明
条件付きプリプロセッシングブロックは、#if、#ifdef、または#ifndefディレクティブで始まり、任意で任意の数の#elif、#elifdef、または#elifndef(C++23以降)ディレクティブを含み、任意で最大1つの#elseディレクティブを含み、#endifディレクティブで終了します。ネストされた条件付きプリプロセッシングブロックは個別に処理されます。
#if、#ifdef、#ifndef、#elif、#elifdef、#elifndef(C++23以降)、および#elseディレクティブのそれぞれは、ネストされた条件付きプリプロセッシングブロックに属さない最初の#elif、#elifdef、#elifndef(C++23以降)、#else、#endifディレクティブまでのコードブロックを制御します。
#if、#ifdef、#ifndefディレクティブは、指定された条件(下記参照)をテストし、それがtrueと評価された場合、制御されたコードブロックをコンパイルします。その場合、その後の#else、#elifdef、#elifndef、(C++23以降)および#elifディレクティブは無視されます。そうでなければ、指定された条件がfalseと評価された場合、制御されたコードブロックはスキップされ、その後の#else、#elifdef、#elifndef、(C++23以降)または#elifディレクティブ(存在する場合)が処理されます。もしその後のディレクティブが#elseであれば、#elseディレクティブによって制御されるコードブロックは無条件にコンパイルされます。そうでなければ、#elif、#elifdef、または#elifndef(C++23以降)ディレクティブは#ifディレクティブであるかのように動作します:条件をチェックし、結果に基づいて制御されたコードブロックをコンパイルまたはスキップし、後者の場合はその後の#elif、#elifdef、#elifndef、(C++23以降)および#elseディレクティブを処理します。条件付きプリプロセッシングブロックは#endifディレクティブで終了します。
[編集] 条件の評価
[編集] #if, #elif
式には、以下を含めることができます。
- 単項演算子として
defined識別子またはdefined (識別子)。結果は、識別子がマクロ名として定義されている場合は1、そうでなければ0です。__has_includeと__has_cpp_attribute(C++20以降)は、この文脈において定義済みマクロ名であるかのように扱われます。(C++17以降) - (C++17以降) ヘッダまたはソースファイルが存在するかどうかを検出する__has_include式。
- (C++20以降) 特定の属性トークンがサポートされているかどうか、およびそのサポートされているバージョンを検出する__has_cpp_attribute式。
すべてのマクロ展開とdefined、__has_include(C++17以降)、および__has_cpp_attribute(C++20以降)式の評価の後、ブールリテラルではないすべての識別子は数値0に置き換えられます(これには、字句的にはキーワードであるが、andのような代替トークンではない識別子が含まれます)。
その後、式は整数定数式として評価されます。
式が非ゼロ値に評価された場合、制御されたコードブロックは含まれ、そうでなければスキップされます。
注:CWG issue 1955の解決まで、#if cond1 ... #elif cond2は#if cond1 ... #elseの後に#if cond2が続くものとは異なります。なぜなら、cond1がtrueの場合、2番目の#ifはスキップされ、cond2は整形式である必要はありませんが、#elifのcond2は有効な式でなければならないからです。CWG 1955以降、スキップされたコードブロックを導く#elifもスキップされます。
[編集] 結合されたディレクティブ
識別子がマクロ名として定義されているかどうかをチェックします。
#ifdef 識別子は、本質的に#if defined 識別子と等価です。
#ifndef 識別子は、本質的に#if !defined 識別子と等価です。
|
|
(C++23から) |
[編集] 注意
#elifdefおよび#elifndefディレクティブはC++23を対象としていますが、実装は適合する拡張として古い言語モードにバックポートすることが推奨されます。
[編集] 例
#define ABCD 2 #include <iostream> int main() { #ifdef ABCD std::cout << "1: yes\n"; #else std::cout << "1: no\n"; #endif #ifndef ABCD std::cout << "2: no1\n"; #elif ABCD == 2 std::cout << "2: yes\n"; #else std::cout << "2: no2\n"; #endif #if !defined(DCBA) && (ABCD < 2*4-3) std::cout << "3: yes\n"; #endif // Note that if a compiler does not support C++23's #elifdef/#elifndef // directives then the "unexpected" block (see below) will be selected. #ifdef CPU std::cout << "4: no1\n"; #elifdef GPU std::cout << "4: no2\n"; #elifndef RAM std::cout << "4: yes\n"; // expected block #else std::cout << "4: no!\n"; // unexpectedly selects this block by skipping // unknown directives and "jumping" directly // from "#ifdef CPU" to this "#else" block #endif // To fix the problem above we may conditionally define the // macro ELIFDEF_SUPPORTED only if the C++23 directives // #elifdef/#elifndef are supported. #if 0 #elifndef UNDEFINED_MACRO #define ELIFDEF_SUPPORTED #else #endif #ifdef ELIFDEF_SUPPORTED #ifdef CPU std::cout << "4: no1\n"; #elifdef GPU std::cout << "4: no2\n"; #elifndef RAM std::cout << "4: yes\n"; // expected block #else std::cout << "4: no3\n"; #endif #else // when #elifdef unsupported use old verbose `#elif defined` #ifdef CPU std::cout << "4: no1\n"; #elif defined GPU std::cout << "4: no2\n"; #elif !defined RAM std::cout << "4: yes\n"; // expected block #else std::cout << "4: no3\n"; #endif #endif }
実行結果の例
1: yes 2: yes 3: yes 4: no! 4: yes
[編集] 欠陥報告
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 1955 | C++98 | 失敗した#elifの式は有効である必要がありました | 失敗した#elifはスキップされます |
[編集] 関連項目
| 条件付きインクルードのCドキュメント
|