その他の範囲
前の章では、主にint
範囲を使用した。実際には、コンテナ、アルゴリズム、および範囲は、ほとんどの場合テンプレートとして実装される。その章のprint()
の例もテンプレートでした。
print()
の実装で欠けている点は、T
がInputRange
の一種である必要があるにもかかわらず、その要件をテンプレート制約で形式化していないことだ(テンプレート制約についてはテンプレートの詳細の章で説明した)。
std.range
モジュールには、テンプレート制約とstatic if
文の両方で役立つテンプレートが含まれている。
範囲種類テンプレート
is
で始まる名前のテンプレートのグループは、型が特定の種類の範囲の要件を満たしているかどうかを判断する。例えば、isInputRange!T
は"T
はInputRange
であるか"という質問に答える。次のテンプレートは、型が特定の一般的な範囲の型であるかどうかを判断するためのものだ。
したがって、print()
のテンプレート制約では、isInputRange
を使用できる。
他のテンプレートとは異なり、isOutputRange
は2つのテンプレートパラメータを取る。1つ目は範囲型、2つ目は要素型である。その範囲型がその要素型の出力を許可する場合、true
を返す。例えば、次の制約は、範囲がdouble
要素を受け入れるOutputRange
でなければならないことを要求するものだ。
static if
と組み合わせて使用すると、これらの制約は、ユーザー定義の範囲の機能も決定することができる。例えば、ユーザー定義の範囲の依存範囲がForwardRange
である場合、ユーザー定義の範囲はその事実を利用して、save()
関数も提供することができる。
これは、既存の範囲の要素の負の値(より正確には、要素の数値の補数)を生成する範囲で見てみよう。まずは、InputRange
関数だけを見てみよう。
注釈:後で説明するように、front
の戻り値の型はElementType!T
としても指定できる。
この範囲の唯一の機能は、元の範囲の最初の要素の負の値を生成するfront
関数にある。
通常どおり、この範囲に対応する便利な関数は次の通りである。
この範囲は、前章で定義されたFibonacciSeries
などと組み合わせて使用できる:
出力には、シリーズの最初の5つの要素の負の値が含まれる:
[0, -1, -1, -2, -3]
当然ながら、Negative
は単なるInputRange
であるため、ForwardRange
を必要とするcycle()
のようなアルゴリズムでは使用できない:
しかし、元の範囲がすでにForwardRange
である場合は、Negative
がsave()
関数を提供しない理由はない。この条件は、static if
文で判断でき、元の範囲がForwardRange
である場合は、save()
を提供することができる。この場合、元の範囲のコピーで構築された新しいNegative
オブジェクトを返すだけで済む。
新しいsave()
関数を追加すると、Negative!FibonacciSeries
もForwardRange
になり、cycle()
の呼び出しをコンパイルできるようになる。
式全体の出力は、フィボナッチ数列の最初の5要素を取り、それらの負を取り、それらを無限に繰り返し、その要素のうち最初の10要素を取る、と表現できる。
[0, -1, -1, -2, -3, 0, -1, -1, -2, -3]
同じアプローチで、元の範囲がこれらの機能をサポートしている場合、Negative
をBidirectionalRange
およびRandomAccessRange
にすることができる。
例えば、スライスで使用すると、[]
演算子で負の要素にアクセスできる。
出力:
-2.75
ElementType
およびElementEncodingType
ElementType
は、範囲の要素の型を提供する。
例えば、次のテンプレート制約には、最初の範囲の要素型に関する要件が含まれている。
前の制約は、I1
がInputRange
、I2
がForwardRange
、O
がI1
の要素型を受け入れるOutputRange
であるかのように記述することができる。
文字列は、実際の文字型に関係なく、常にUnicode文字の範囲であるため、常にdchar
の範囲になる。つまり、ElementType!string
およびElementType!wstring
もdchar
になる。そのため、テンプレートで必要な場合、文字列範囲の実際のUTFエンコード型は、ElementEncodingType
で取得できる。
その他の範囲テンプレート
std.range
モジュールには、Dの他のコンパイル時機能と組み合わせて使用できる多くの範囲テンプレートが用意されている。以下は一例だ。
-
isInfinite
: 範囲が無限かどうか -
hasLength
length
: 範囲が無限であるかどうか -
hasSlicing
: 範囲がスライシング(例:a[x..y]
-
hasAssignableElements
:front
の戻り値の型が代入可能かどうか -
hasSwappableElements
: 範囲の要素が入れ替え可能かどうか(例:std.algorithm.swap
-
hasMobileElements
: 範囲の要素が移動可能かどうか(例:std.algorithm.move
を使用)これは、範囲が
moveFront()
、moveBack()
、またはmoveAt()
のいずれかを満たすことを意味する。要素の移動はコピーよりも通常高速であるため、hasMobileElements
の結果に応じて、範囲はmove()
を呼び出すことでより高速な操作を提供できる。 -
hasLvalueElements
: 範囲の要素がl値であるかどうか (大まかに言えば、要素が実際の要素のコピーでも、その場で作られる一時オブジェクトでもないかどうか)例えば、
hasLvalueElements!FibonacciSeries
は、FibonacciSeries
の要素はそれ自体としては存在せず、front
によって返されるメンバーcurrent
のコピーであるため、false
である。同様に、hasLvalueElements!(Negative!(int[]))
は、int
スライスには実際の要素は存在するが、Negative
によって表される範囲はそれらの要素へのアクセスを提供せず、実際のスライスの要素の負の符号を持つコピーを返すため、false
だ。一方、hasLvalueElements!(int[])
は、スライスは配列の実際の要素へのアクセスを提供するため、true
だ。
次の例では、isInfinite
を利用して、元の範囲が無限の場合にempty
をenum
として提供し、Negative!T
も無限であることをコンパイル時に認識させる。
inputRangeObject()
を使用した実行時多態性outputRangeObject()
ほとんどテンプレートとして実装されている範囲は、コンパイル時の多態性を示す。この章および前の章の例では、この多態性を活用してきた。コンパイル時の多態性と実行時の多態性の違いについては、"テンプレートの詳細"の"コンパイル時の多態性"のセクションを参照。
コンパイル時多態性は、テンプレートのすべてのインスタンス化が異なる型であるという事実に対処する必要がある。例えば、take()
テンプレートの戻り値の型は、元の範囲に直接関連している。
出力:
Take!(Negative!(int[]))
Take!(FibonacciSeries)
この事実の当然の結果として、異なる範囲型は互いに割り当てることができない。以下は、2つのInputRange
範囲間のこの非互換性の例だ。
予想通り、コンパイルエラーはFibonacciSeries
とNegative!(int[])
が互換性がないことを示している:
しかし、実際の範囲の型は異なるものの、どちらも int
の範囲であるため、この非互換性は不必要な制限であると見なすことができる。使用上の観点からは、どちらの範囲も単にint
要素を提供しているだけなので、それらの要素を生成する実際のメカニズムは重要ではないはずだ。
Phobosは、inputRangeObject()
およびoutputRangeObject()
によってこの問題に対処している。inputRangeObject()
を使用すると、範囲を特定の要素の特定の型の特定の種類の範囲として表現することができる。この機能を使用すると、範囲の実際の型に関係なく、範囲を、例えば、 int
要素のInputRange
として使用することができる。
inputRangeObject()
は、すべての非出力範囲(InputRange
、ForwardRange
、BidirectionalRange
、RandomAccessRange
)をサポートする十分な柔軟性を持っている。この柔軟性のため、返されるオブジェクトはauto
で定義できない。必要な範囲の正確な種類は明示的に指定する必要がある:
別の例として、範囲を int
要素のForwardRange
として使用する必要がある場合、その型はForwardRange!int
と明示的に指定する必要がある。
この例では、ForwardRange
が実際に範囲として使用できることを証明するために、save()
を呼び出している。
同様に、outputRangeObject()
はOutputRange
範囲でも機能し、特定の型の要素を受け入れるOutputRange
として使用することができる。
要約
std.range
モジュールには、多くの有用な範囲テンプレートが含まれている。- これらのテンプレートの一部は、元の範囲の機能に応じてテンプレートの機能を拡張する。
inputRangeObject()
およびoutputRangeObject()
は、実行時の多態性を提供し、さまざまな型の範囲を、特定の型の要素の特定の種類の範囲として使用できるようにする。