条件付きコンパイル
条件付きコンパイルは、特定のコンパイル時条件に応じてプログラムの一部を特別な方法でコンパイルするためのものだ。場合によっては、プログラムのセクション全体を削除してコンパイルしない必要がある場合もある。
条件付きコンパイルでは、コンパイル時に評価可能な条件チェックが行われる。if、for、whileなどの実行時条件文は、条件付きコンパイル機能ではない。
以前の章で既に遭遇したいくつかの機能は、条件付きコンパイルと見なすことができる:
unittest‑unittestコンパイラスイッチが有効な場合のみ、ブロックがコンパイルされ実行される。- 契約プログラミングブロック
in、out、およびinvariantは、-releaseコンパイルスイッチが有効になっていない場合にのみ有効になる。
ユニットテストと契約はプログラムの正確性に関するものであり、プログラムに含めるかどうかによってプログラムの動作が変わるべきではない。
Dの条件付きコンパイル専用の機能は以下の通りだ。
debugversionstatic ifis式__traits
is式については、次の章で説明する。
debug
debugは、プログラムの開発中に役立つ。debugとマークされた式および文は、-debugコンパイラスイッチが有効になっている場合にのみプログラムにコンパイルされる。
else句はオプションである。
上記の単一の式とコードブロックは、-debugコンパイラスイッチが有効になっている場合にのみコンパイルされる。
プログラムに、"加算"、"減算"などのメッセージを出力する文を追加してきた。このようなメッセージ(別名、ログや ログメッセージ)は、プログラムが実行する手順を視覚化することで、エラーの発見に役立つ。
テンプレート章で説明したbinarySearch()関数を思い出そう。次の関数は、意図的に誤ったものである。
インデックス42の値は6であるにもかかわらず、プログラムは誤って1と報告している:
| インデックス | 1 |
|---|
プログラム内のバグを特定する一つの方法は、出力にメッセージを表示する行を挿入することだ:
プログラムの出力には、プログラムが実行するステップが含まれるようになった:
[-100, 0, 1, 2, 7, 10, 42, 365, 1000]から42を検索
インデックス4を考慮
後半にある必要がある
42を[10, 42, 365, 1000]から検索
インデックス2を考慮
前半にある必要がある
42を[10, 42] から検索
インデックス1を考慮
インデックス1で42が見つかった
インデックス: 1
前の出力が、プログラマーがバグを見つけるのに実際に役立ったと仮定しよう。バグを見つけて修正したら、writefln()式はもはや必要ないことは明らかだ。しかし、これらの行を削除することは、将来再び役立つ可能性があるため、無駄であるとも考えられる。
これらの行を完全に削除する代わりに、debugとしてマークすることができる:
これらの行は、-debugコンパイラスイッチが有効になっている場合にのみプログラムに含められる:
debug(tag)
プログラム内に、おそらくは関連のない部分に、debugキーワードが多数存在する場合、出力結果が過密になる可能性がある。それを回避するために、debug文に名前(タグ)を付けて、プログラムに選択的に含めるようにすることができる。
タグが付けられたdebug文は、-debug=tagコンパイラスイッチで有効になる:
debugブロックにもタグを付けることができる:
一度に複数のdebugタグを有効にすることもできる:
その場合、binarySearchとstackContainerの両方のデバッグ文およびブロックが組み込まれる。
debug(level)
debug文を数値レベルで関連付ける方が便利な場合もある。レベルを高くすると、より詳細な情報を得ることができる。
指定したレベル以下であるdebug式およびブロックがコンパイルされる。
次のコンパイルでは、より詳細な情報が提供される:
version(tag)および version(level)
versionはdebugと類似しており、同じように使用される:
else句はオプションである。
versionは基本的にdebugと同じように機能するが、別々のキーワードを使用することで、関連のない用途を区別しやすくなる。
debug同様、複数のversionを有効にすることができる:
versionタグには多くの定義済みタグがあり、その完全なリストは条件付きコンパイルの仕様で確認できる。以下は、その一部を抜粋したリストだ。
| コンパイラ | DigitalMars |
|---|---|
| GNU | |
| LDC | |
| SDC | |
| オペレーティングシステム | Windows |
| Win32 | |
| Win64 | |
| linux | |
| OSX | |
| Posix | |
| FreeBSD | |
| OpenBSD | |
| NetBSD | |
| DragonFlyBSD | |
| BSD | |
| Solaris | |
| AIX | |
| Haiku | |
| SkyOS | |
| SysV3 | |
| SysV4 | |
| Hurd | |
| CPUのエンディアン | LittleEndian |
| BigEndian | |
| 有効なコンパイラスイッチ | D_Coverage |
| D_Ddoc | |
| D_InlineAsm_X86 | |
| D_InlineAsm_X86_64 | |
| D_LP64 | |
| D_PIC | |
| D_X32 | |
| D_HardFloat | |
| D_SoftFloat | |
| D_SIMD | |
| D_Version2 | |
| D_NoBoundsChecks | |
| unittest | |
| assert | |
| CPUアーキテクチャ | X86 |
| X86_64 | |
| プラットフォーム | Android |
| Cygwin | |
| MinGW | |
| ARM | |
| ARM_Thumb | |
| ARM_Soft | |
| ARM_SoftFP | |
| ARM_HardFP | |
| ARM64 | |
| PPC | |
| PPC_SoftFP | |
| PPC_HardFP | |
| PPC64 | |
| IA64 | |
| MIPS | |
| MIPS32 | |
| MIPS64 | |
| MIPS_O32 | |
| MIPS_N32 | |
| MIPS_O64 | |
| MIPS_N64 | |
| MIPS_EABI | |
| MIPS_NoFloat | |
| MIPS_SoftFloat | |
| MIPS_HardFloat | |
| SPARC | |
| SPARC_V8Plus | |
| SPARC_SoftFP | |
| SPARC_HardFP | |
| SPARC64 | |
| S390 | |
| S390X | |
| HPPA | |
| HPPA64 | |
| SH | |
| SH64 | |
| Alpha | |
| Alpha_SoftFP | |
| Alpha_HardFP | |
| ... | ... |
さらに、以下の2つの特別なversionタグがある:
事前定義されたversionタグの使用例として、システム用の改行文字シーケンスを決定するためのstd.asciiモジュールから一部を抜粋して(ここではフォーマットを変更して)以下に示す(static assertについては後で説明する)。
debugとversionに識別子を割り当てる
変数と同様に、debugおよびversionには識別子を割り当てることができる。変数とは異なり、この割り当ては値を変更するのではなく、指定した識別子を有効にする。
上記のdebug(everything)ブロック内の代入は、指定されたすべての識別子を有効にする:
static if
static ifは、if文のコンパイル時同等である。
if文と同様に、static ifは論理式を取り、それを評価する。if文とは異なり、static ifは実行フローとは関係なく、コードの一部をプログラムに含めるかどうかを決定する。
論理式は、コンパイル時に評価可能でなければならない。論理式がtrueと評価された場合、static if内のコードがコンパイルされる。条件がfalseの場合、そのコードは、記述されていないかのようにプログラムには含まれない。論理式は、通常、is式および__traitsを利用している。
static ifモジュールスコープまたはstruct、 class、テンプレートなどの定義内に出現する。オプションで、else句も含まれることがある。
is式を使用して、単純なテンプレートでstatic ifを使ってみよう。static ifの他の例については、次の章で説明する。
コードによると、MyTypeは2つの型、floatまたはdoubleでのみ使用できる。doWork()の戻り値の型は、テンプレートがfloatまたはdoubleのいずれでインスタンス化されるかによって選択される。
floatの戻り値の型はdoubleである。
doubleの戻り値の型はrealである。
static if節を連鎖する場合は、else static ifを記述する必要があることに注意。そうしないと、else ifと記述すると、そのif条件式がコードに挿入され、当然、実行時に実行されてしまう。
static assert
これは条件付きコンパイル機能ではないが、static assertをここで紹介することにした。
static assertは、assertとコンパイル時に同等である。条件式がfalseの場合、そのアサーションの失敗によりコンパイルは中止される。
static ifと同様に、static assertはプログラム内の任意のスコープに現れることができる。
上記のプログラムで、static assertの例を見た。そこでは、Tがfloatまたはdouble以外の型の場合、コンパイルは中止される。
static assertに与えられたメッセージと共にコンパイルが中止される:
別の例として、特定のアルゴリズムが、特定のサイズの倍数である型でのみ動作するとする。このような条件は、static assertによってコンパイル時に確保できる。
この関数がcharで呼び出された場合、次のエラーメッセージが表示され、コンパイルは中止される。
このようなテストにより、関数が互換性のない型で動作して、誤った結果が生じるのを防ぐことができる。
static assertは、コンパイル時に評価可能な任意の論理式で使用できる。
型トレイト
__traitsキーワードとstd.traitsモジュールは、コンパイル時に型および式に関する情報を提供する。
コンパイラによって収集される情報の一部は、__traitsによってプログラムで使用可能になる。その構文には、traitsキーワードと、そのキーワードに関連するパラメータが含まれる。
キーワードは、照会される情報を指定する。パラメータは型または式で、その意味はそれぞれのキーワードによって決まる。
__traitsで収集できる情報は、テンプレートで特に有用だ。例えば、isArithmeticキーワードは、特定のテンプレートパラメータTが算術型であるかどうかを判断できる。
同様に、std.traitsモジュールは、そのテンプレートを通じてコンパイル時に情報を提供する。例えば、std.traits.isSomeCharは、そのテンプレートパラメータが文字型の場合、trueを返す。
詳細については、 __traitsのドキュメントおよび std.traitsのドキュメントを参照。
要約
debugとして定義されたコードは、-debugコンパイラスイッチが使用された場合のみプログラムに含められる。versionで定義されたコードは、対応する-versionコンパイラスイッチが使用された場合のみプログラムに含められる。static ifは、コンパイル時に実行されるif文と似ている。これは、特定のコンパイル時の条件に応じて、プログラムにコードを導入する。static assertコンパイル時にコードに関する仮定を検証する。__traitsおよびstd.traitsは、コンパイル時に型に関する情報を提供する。