型変換
変数は、それらが参加する式と互換性がある必要がある。これまで見てきたプログラムから明らかなように、Dは静的型付け言語である。つまり、型の互換性はコンパイル時に検証される。
これまでに記述した式は、すべて型が互換性があった。そうでないと、コンパイラによってコードが拒否されてしまうからだ。以下は、型が互換性のないコードの例だ。
加算演算において、char[]
とint
の型が互換性がないため、コンパイラはコードを拒否する。
エラー: ((slice) + (5))の型が一致しない: 'char[]'と'int'
型の非互換性は、型が異なることを意味するものではない。異なる型は、式内で安全に使用することができる。例えば、int
変数は、double
値の代わりに使用することができる。
sum
とincrement
は異なる型だが、double
変数をint
値で加算することは合法であるため、上記のコードは有効である。
自動型変換
自動型変換は、暗黙の型変換とも呼ばれる。
上記の式では、double
とint
は互換性のある型だが、加算演算はマイクロプロセッサレベルでは特定の型として評価される必要がある。浮動小数点型の章で覚えているとおり、64ビット型double
は32ビット型int
よりも幅が広い(大きい)。さらに、int
に収まる値は、double
にも収まる。
コンパイラは、型が一致しない式を検出すると、まず式の各部分を共通の型に変換してから、式全体を評価する。コンパイラによって実行される自動変換は、データの損失がない方向に行われる。例えば、double
は、int
が保持できる値をすべて保持できるが、その逆は当てはまらない。上記の+=
演算は、int
の値はすべてdouble
に安全に変換できるため、正常に動作する。
変換の結果として自動的に生成された値は、常に匿名(そして多くの場合一時的な)変数である。元の値は変更されない。例えば、上記の+=
での自動変換では、increment
の型は変更されない。これは常にint
である。むしろ、double
型の値increment
を使用して、一時的な値が構築される。バックグラウンドで実行される変換は、次のコードと同等である。
コンパイラは、int
の値を一時的なdouble
の値に変換し、その値を操作で使用する。この例では、一時変数は+=
操作の間だけ存在する。
自動変換は、算術演算だけに限定されない。型が自動的に他の型に変換されるケースは他にもある。変換が有効である限り、コンパイラは型変換を利用して、式で値を使用できるようにする。例えば、int
パラメータにbyte
値を渡すことができる。
上記のコードでは、まず一時的なint
値が構築され、その値で関数が呼び出される。
整数の型変換
次の表の左側にある型の値は、その実際の型として算術式に参加することはない。各型は、まず表の右側にある型に昇格される。
変換元 | 変換先 |
---|---|
bool | int |
byte | int |
ubyte | int |
short | int |
ushort | int |
char | int |
wchar | int |
dchar | uint |
整数昇格は、enum
値にも適用される。
整数昇格が行われる理由は、歴史的な理由(この規則がCから来ている)と、マイクロプロセッサの自然な算術型がint
であるという事実の両方がある。例えば、次の2つの変数はどちらもubyte
だが、加算演算は、両方の値が個別にint
に昇格された後にのみ実行される。
出力:
int
変数a
とb
の型は変化しないことに注意しよう。加算演算の実行中、その値だけが一時的にint
に昇格されるだけだ。
算術変換
算術演算には、他にも変換規則が適用される。一般に、自動算術変換は、より狭い型からより広い型へと、安全な方向に適用される。この規則は覚えやすく、ほとんどの場合に正しいが、自動変換規則は非常に複雑であり、符号付きから符号なしへの変換の場合、バグのリスクを伴う。
算術変換のルールは次の通りだ。
- 値の1つが
real
の場合、もう1つの値はreal
- それ以外の場合、いずれかの値が
double
の場合、もう一方の値はdouble
- それ以外の場合、いずれかの値が
float
の場合、もう一方の値はfloat
- それ以外の場合、上記の表に従って整数変換が適用され、その後、以下の規則が適用される:
- 両方の型が同じ場合は、それ以上の手順は必要ない。
- 両方の型が符号付き、または両方の型が符号なしの場合、より狭い値がより広い型に変換される
- 符号付き型が符号なし型よりも広い場合、符号なし値は符号付き型に変換される
- それ以外の場合は、符号付き型が符号なし型に変換される
残念ながら、上記の最後のルールは subtle bugs を引き起こす可能性がある:
意外にも、出力は-1ではなくsize_t.max
となる:
18446744073709551615
(0 - 1 + 0)
は-1と計算されると思うかもしれないが、上記の規則によると、式全体の型はint
ではなくsize_t
であり、size_t
は負の値を保持できないため、結果はオーバーフローしてsize_t.max
になる。
スライス変換
便宜上、固定長配列は関数を呼び出すときに自動的にスライスに変換することができる。
bar()
固定長配列のすべての要素のスライスを受け取り、それを出力する:
[1, 2]
警告:関数が後で使用するためにスライスを格納する場合、ローカル固定長配列をスライスとして渡してはいけない。例えば、次のプログラムには、foo()
が終了すると、bar()
が格納するスライスが無効になるため、バグがある。
このようなバグの結果は、未定義の動作になる。実行例では、array
の要素だったメモリが、すでに他の目的のために再利用されていることがわかる。
bar内部 : [1, 2] ← 実際の要素
main内部: [4396640, 0] ← 未定義動作の現れ
const
変換
関数パラメータの章で見たように、参照型は同じ型のconst
に自動的に変換される。const
への変換は、型の幅が変わらず、const
は変数を変更しないという約束があるため、安全だ。
上記の変更可能なgreeting
は、parenthesized()
に渡されると、自動的にconst char[]
に変換される。
前述のように、逆の変換は自動的に行われない。const
参照は、自動的に変更可能な参照に変換されない。
このトピックは参照についてのみ説明していることに注意。値型の変数はコピーされるから、コピーを通じて元の変数に影響を与えることはどうやってもできない。
上記のconst
から変更可能への変換は、コピーは元の参照ではないため、合法である。
immutable
変換
immutable
は変数が変更できないことを指定しているため、immutable
からimmutable
への変換も、 から への変換も自動的には行われない:
上記のconst
の変換と同様、このトピックも参照型についてのみ扱う。値型変数はとにかくコピーされるため、immutable
への変換および からの変換は有効だ。
enum
変換
列挙型の章で見たように、enum
は名前付き定数を定義するために使用される:
上記では値が明示的に指定されていないため、enum
メンバーの値は0から始まり、自動的に1ずつ加算されることに注意。したがって、Suit.clubs
の値は3になる。
enum
値は自動的に整数型に変換される。例えば、次の計算では、Suit.hearts
の値は1とみなされ、結果は11になる。
逆の変換は自動的に行われない。整数値は、対応するenum
値に自動的に変換されない。例えば、以下のsuit
変数はSuit.diamonds
になることが予想されるが、このコードはコンパイルできない。
後で説明するように、整数からenum
値への変換は可能だが、明示的に指定する必要がある。
bool
変換
bool
は論理式の自然な型だが、値は2つしかないので、1ビットの整数と見なすことができ、場合によっては整数のように振る舞う。false
とtrue
は、それぞれ0と1に自動的に変換される。
リテラル値に関しては、2つの特別なリテラル値についてのみ、逆の変換が自動的に行われる。0と1は、それぞれfalse
とtrue
に自動的に変換される。
その他のリテラル値は、bool
に自動的に変換することはできない。
一部の文では、論理式を使用する。if
、while
などだ。このような文の論理式では、bool
だけでなく、他のほとんどの型も使用できる。値0は自動的にfalse
に変換され、0以外の値は自動的にtrue
に変換される。
同様に、null
参照は自動的にfalse
に変換され、非null
参照は自動的にtrue
に変換される。これにより、実際に使用する前に参照が非null
であることを簡単に確認できる。
明示的な型変換
上記で見たように、自動変換が利用できないケースがある:
- 広い型から狭い型への変換
const
からmutableへの変換immutable
変換- 整数から
enum
値への変換 - など
このような変換が安全であることがわかっている場合、プログラマは次のいずれかの方法で型変換を明示的に要求することができる。
- コンストラクタ構文
std.conv.to
関数std.exception.assumeUnique
関数cast
演算子
コンストラクタ構文
struct
およびclass
の構文は、他の型でも使用できる。
例えば、次の変換は、おそらく除算演算の小数部分を保持するために、int
値からdouble
値を作成する。
to()
ほとんどの変換の場合
これまで主に値をstring
に変換するために使用してきたto()
関数は、実際には他の多くの型にも使用できる。その完全な構文は次の通りだ。
テンプレートであるto()
は、ショートカットテンプレートパラメータ表記を利用できる。 変換先の型が1つのトークン (通常は1 単語) だけで構成されている場合は、最初の括弧を省略して呼び出すことができる。
次のプログラムは、double
の値をshort
に、string
の値をint
に変換しようとしている。
すべてのdouble
値がshort
として表現できるわけではなく、また、すべてのstring
がint
として表現できるわけではないため、これらの変換は自動的に行われない。変換が実際に安全である、あるいは潜在的な影響が許容範囲内であることがプログラマにわかっている場合は、to()
を使用して型を変換することができる。
short
は小数値を保持できないため、変換された値は-1になることに注意。
to()
は安全である。変換が不可能な場合は例外をスローする。
assumeUnique()
高速なimmutable
変換のため
to()
immutable
の変換も実行可能:
immutableSlice
の要素が決して変更されないことを保証するために、slice
と同じ要素を共有することはできない。そのため、to()
は、上記のimmutable
要素を含む追加のスライスを作成する。そうしないと、slice
の要素を変更すると、immutableSlice
の要素も変更されてしまう。この動作は、配列の.idup
プロパティと同じだ。
immutableSlice
の要素がslice
の要素のコピーであることは、それぞれの最初の要素のアドレスを確認することで確認できる。
このコピーは不要な場合もあり、場合によってはプログラムの速度が著しく低下する可能性がある。この例として、immutable
スライスを受け取る次の関数を見てみよう。
上記のプログラムは、呼び出し元がcalculate()
にimmutable
引数を渡していないため、コンパイルできない。先ほど見たように、immutable
スライスをto()
で作成できる:
しかし、numbers
はこの引数を生成するためにのみ必要であり、関数の呼び出し後は決して使用されない場合、その要素をimmutableNumbers
にコピーする必要はない。assumeUnique()
は、スライスimmutable
の要素をコピーせずに、スライス を作成する。
assumeUnique()
既存の要素へのアクセスを提供する新しいスライスを返す。また、元のimmutable
スライス をnull
に変更し、誤って変更されるのを防ぐ。
cast
演算子
to()
とassumeUnique()
は、プログラマーが使用できる変換演算子cast
を利用している。
cast
演算子は、括弧で囲まれた宛先型を受け取る。
cast
は、to()
では安全に実行できない変換に対しても強力な機能だ。例えば、to()
は、実行時に次の変換で失敗する。
整数値が有効なenum
値に対応しているかどうか、あるいは整数値をbool
として扱うことが妥当であるかどうかは、プログラマーしか判断できない場合がある。cast
演算子は、プログラムのロジックに従って変換が正しいことが分かっている場合に使用できる。
cast
ポインタ型との変換では、これが唯一の選択肢である。
まれだが、一部のCライブラリインターフェイスでは、ポインタ値をポインタ型以外の型として格納する必要がある。変換によって実際の値が保持されることが保証されている場合は、cast
を使用してポインタ型とポインタ型以外の型の間で変換することもできる。
要約
- 自動型変換は、ほとんどの場合、安全な方向、つまりより狭い型からより広い型へ、および変更可能から
const
へ変換される。 - ただし、符号なし型への変換は、符号なし型には負の値がないため、予期しない結果になる場合がある。
enum
型は自動的に整数値に変換されるが、その逆の変換は自動的には行われない。false
およびtrue
は、それぞれ0および1に自動的に変換される。同様に、ゼロ値はfalse
に、ゼロ以外の値はtrue
に自動的に変換される。null
参照は自動的にfalse
に変換され、非null
参照は自動的にtrue
に変換される。- 明示的な変換には、構築構文を使用できる。
to()
ほとんどの明示的な変換をカバーしている。assumeUnique()
コピーせずにimmutable
に変換する。cast
演算子は、最も強力な変換ツールだ。