条件付きコンパイル
条件付きコンパイルは、特定のコンパイル時条件に応じてプログラムの一部を特別な方法でコンパイルするためのものだ。場合によっては、プログラムのセクション全体を削除してコンパイルしない必要がある場合もある。
条件付きコンパイルでは、コンパイル時に評価可能な条件チェックが行われる。if
、for
、while
などの実行時条件文は、条件付きコンパイル機能ではない。
以前の章で既に遭遇したいくつかの機能は、条件付きコンパイルと見なすことができる:
unittest
‑unittest
コンパイラスイッチが有効な場合のみ、ブロックがコンパイルされ実行される。- 契約プログラミングブロック
in
、out
、およびinvariant
は、-release
コンパイルスイッチが有効になっていない場合にのみ有効になる。
ユニットテストと契約はプログラムの正確性に関するものであり、プログラムに含めるかどうかによってプログラムの動作が変わるべきではない。
Dの条件付きコンパイル専用の機能は以下の通りだ。
debug
version
static if
is
式__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
は、コンパイル時に型に関する情報を提供する。