その他の範囲
前の章では、主に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: 範囲が無限かどうか -
hasLengthlength: 範囲が無限であるかどうか -
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()は、実行時の多態性を提供し、さまざまな型の範囲を、特定の型の要素の特定の種類の範囲として使用できるようにする。