Başka Aralık Olanakları
Bundan önceki bölümdeki çoğu aralık örneğinde int
aralıkları kullandık. Aslında topluluklar, algoritmalar, ve aralıklar, hep şablonlar olarak gerçekleştirilirler. Biz de bir önceki bölümde yazdır()
işlevini şablon olarak tanımladığımız için farklı InputRange
aralıklarıyla kullanabilmiştik:
void yazdır(T)(T aralık) { // ... }
yazdır()
'ın bir eksiği, şablon parametresinin bir InputRange
olması gerektiği halde bunu bir şablon kısıtlaması ile belirtmiyor olmasıdır. (Şablon kısıtlamalarını Ayrıntılı Şablonlar bölümünde görmüştük.)
std.range
modülü, hem şablon kısıtlamalarında hem de static if
deyimlerinde yararlanılmak üzere çok sayıda yardımcı şablon içerir.
Aralık çeşidi şablonları
Bu şablonların "öyle midir" anlamına gelen is
ile başlayanları, belirli bir türün o aralık çeşidinden olup olmadığını belirtir. Örneğin isInputRange!T
, "T
bir InputRange
midir" sorusunu yanıtlar. Aralık çeşidini sorgulayan şablonlar şunlardır:
yazdır()
işlevinin şablon kısıtlaması isInputRange
'den yararlanarak şöyle yazılır:
void yazdır(T)(T aralık) if (isInputRange!T) { // ... }
Bunlar arasından isOutputRange
, diğerlerinden farklı olarak iki şablon parametresi alır: Birincisi desteklenmesi gereken aralık türünü, ikincisi ise desteklenmesi gereken eleman türünü belirler. Örneğin aralığın double
türü ile uyumlu olan bir çıkış aralığı olması gerektiği şöyle belirtilir:
void birİşlev(T)(T aralık) if (isOutputRange!(T, double)) { // ... }
O kısıtlamayı T
bir OutputRange
ise ve double
türü ile kullanılabiliyorsa diye okuyabiliriz.
static if
ile birlikte kullanıldıklarında bu şablonlar kendi yazdığımız aralıkların ne derece yetenekli olabileceklerini de belirlerler. Örneğin asıl aralık ForwardRange
olduğunda save()
işlevine de sahip olacağından, kendi yazdığımız özel aralık türünün de save()
işlevini sunmasını o işlevden yararlanarak sağlayabiliriz.
Bunu görmek için kendisine verilen aralıktaki değerlerin ters işaretlilerini üreten bir aralığı önce bir InputRange
olarak tasarlayalım:
import std.range; struct Tersi(T) if (isInputRange!T) { T aralık; bool empty() { return aralık.empty; } auto front() { return -aralık.front; } void popFront() { aralık.popFront(); } }
Not: front
'un dönüş türü olarak auto
yerine biraz aşağıda göreceğimiz ElementType!T
de yazılabilir.
Bu aralığın tek özelliği, front
işlevinde asıl aralığın başındaki değerin ters işaretlisini döndürmesidir.
Çoğu aralık türünde olduğu gibi, kullanım kolaylığı açısından bir de yardımcı işlev yazalım:
Tersi!T tersi(T)(T aralık) {
return Tersi!T(aralık);
}
Bu aralık örneğin bir önceki bölümde gördüğümüz FibonacciSerisi
aralığıyla birlikte kullanılmaya hazırdır:
struct FibonacciSerisi { int baştaki = 0; int sonraki = 1; enum empty = false; int front() const { return baştaki; } void popFront() { const ikiSonraki = baştaki + sonraki; baştaki = sonraki; sonraki = ikiSonraki; } FibonacciSerisi save() const { return this; } } // ... writeln(FibonacciSerisi().take(5).tersi);
Çıktısı, aralığın ilk 5 elemanının ters işaretlilerini içerir:
[0, -1, -1, -2, -3]
Doğal olarak, Tersi
bu tanımıyla yalnızca bir InputRange
olarak kullanılabilir ve örneğin ForwardRange
gerektiren cycle()
gibi algoritmalara gönderilemez:
writeln(FibonacciSerisi()
.take(5)
.tersi
.cycle // ← derleme HATASI
.take(10));
Oysa, asıl aralık FibonacciSerisi
gibi bir ForwardRange
olduğunda Tersi
'nin de save()
işlevini sunamaması için bir neden yoktur. Bu durum derleme zamanında static if
ile denetlenir ve ek işlevler asıl aralığın yetenekleri doğrultusunda tanımlanırlar. Bu durumda, asıl aralığın bir kopyası ile kurulmuş olan yeni bir Tersi
nesnesi döndürmek yeterlidir:
struct Tersi(T) if (isInputRange!T) { // ... static if (isForwardRange!T) { Tersi save() { return Tersi(aralık.save()); } } }
Yukarıdaki ek işlev sayesinde Tersi!FibonacciSerisi
de artık bir ForwardRange
olarak kabul edilir ve yukarıdaki cycle()
satırı artık derlenir:
writeln(FibonacciSerisi()
.take(5)
.tersi
.cycle // ← artık derlenir
.take(10));
Çıktısı, Fibonacci serisinin ilk 5 elemanının ters işaretlilerinin sürekli olarak tekrarlanmasından oluşan aralığın ilk 10 elemanıdır:
[0, -1, -1, -2, -3, 0, -1, -1, -2, -3]
Tersi
aralığının duruma göre BidirectionalRange
ve RandomAccessRange
olabilmesi de aynı yöntemle sağlanır:
struct Tersi(T) if (isInputRange!T) { // ... static if (isBidirectionalRange!T) { auto back() { return -aralık.back; } void popBack() { aralık.popBack(); } } static if (isRandomAccessRange!T) { auto opIndex(size_t sıraNumarası) { return -aralık[sıraNumarası]; } } }
Böylece örneğin dizilerle kullanıldığında elemanlara []
işleci ile erişilebilir:
auto d = [ 1.5, 2.75 ]; auto e = tersi(d); writeln(e[1]);
Çıktısı:
-2.75
ElementType
ve ElementEncodingType
ElementType
, aralıktaki elemanların türünü bildiren bir şablondur. ElementType!T
, "T
aralığının eleman türü" anlamına gelir.
Örneğin, belirli türden iki aralık alan ve bunlardan birincisinin elemanlarının türü ile uyumlu olan bir çıkış aralığı gerektiren bir işlevin şablon kısıtlaması şöyle belirtilebilir:
void işle(G1, G2, Ç)(G1 giriş1, G2 giriş2, Ç çıkış) if (isInputRange!G1 && isForwardRange!G2 && isOutputRange!(Ç, ElementType!G1)) { // ... }
Yukarıdaki şablon kısıtlamasını şöyle açıklayabiliriz: G1
bir InputRange
ve G2
bir ForwardRange
olmalıdır; ek olarak, Ç
de G1
'in elemanlarının türü ile kullanılabilen bir OutputRange
olmalıdır.
Dizgiler aralık olarak kullanıldıklarında elemanlarına harf harf erişildiği için dizgi aralıklarının eleman türü her zaman için dchar
'dır. Bu yüzden dizgilerin UTF kodlama türü ElementType
ile belirlenemez. UTF kodlama türünü belirlemek için ElementEncodingType
kullanılır. Örneğin bir wchar
dizgisinin ElementType
'ı dchar
, ElementEncodingType
'ı da wchar
'dır.
Başka aralık nitelikleri
std.range
modülü aralıklarla ilgili başka şablon olanakları da sunar. Bunlar da şablon kısıtlamalarında ve static if
deyimlerinde kullanılırlar.
-
isInfinite
: Aralık sonsuzsatrue
üretir. -
hasLength
: Aralığınlength
niteliği varsatrue
üretir. -
hasSlicing
: Aralığına[x..y]
biçiminde dilimi alınabiliyorsatrue
üretir. -
hasAssignableElements
: Aralığın elemanlarına değer atanabiliyorsatrue
üretir. -
hasSwappableElements
: Aralığın elemanlarıstd.algorithm.swap
ile değiş tokuş edilebiliyorsatrue
üretir. -
hasMobileElements
: Aralığın elemanlarıstd.algorithm.move
ile aktarılabiliyorsatrue
üretir.Bu, aralık çeşidine bağlı olarak baştaki elemanı aktaran
moveFront()
'un, sondaki elemanı aktaranmoveBack()
'in, veya rastgele bir elemanı aktaranmoveAt()
'in mevcut olduğunu belirtir. Aktarma işlemi kopyalama işleminden daha hızlı olduğundanhasMobileElements
niteliğinin sonucuna bağlı olarak bazı işlemlermove()
ile daha hızlı gerçekleştirilebilirler. -
hasLvalueElements
: Aralığın elemanları sol değer olarak kullanılabiliyorsatrue
üretir. Bu kavramı, aralığın elemanları gerçekte var olan elemanlara referans iseler diye düşünebilirsiniz.Örneğin
hasLvalueElements!FibonacciSerisi
'nin değerifalse
'tur çünküFibonacciSerisi
aralığının elemanları gerçekte var olan elemanlar değillerdir; hesaplanarak oluşturulurlar. Benzer şekildehasLvalueElements!(Tersi!(int[]))
'in değeri defalse
'tur çünkü o aralığın da gerçek elemanları yoktur. Öte yandan,hasLvalueElements!(int[])
'in değeritrue
'dur çünkü dilimler gerçekte var olan elemanlara erişim sağlarlar.
Örneğin empty
, isInfinite!T
'nin değerine bağlı olarak farklı biçimde tanımlanabilir. Böylece, asıl aralık sonsuz olduğunda Tersi!T
'nin de derleme zamanında sonsuz olması sağlanmış olur:
struct Tersi(T) if (isInputRange!T) { // ... static if (isInfinite!T) { // Tersi!T de sonsuz olur enum empty = false; } else { bool empty() { return aralık.empty; } } // ... } static assert( isInfinite!(Tersi!FibonacciSerisi)); static assert(!isInfinite!(int[]));
Çalışma zamanı çok şekilliliği için inputRangeObject()
ve outputRangeObject()
Aralıklar, şablonların getirdiği derleme zamanı çok şekilliliğine sahiptirler. Biz de bir önceki ve bu bölümdeki çoğu örnekte bu olanaktan yararlandık. (Not: Derleme zamanı çok şekilliliği ile çalışma zamanı çok şekilliliğinin farklarını Ayrıntılı Şablonlar bölümündeki "Derleme zamanı çok şekilliliği" başlığında görmüştük.)
Derleme zamanı çok şekilliliğinin bir etkisi, şablonun her farklı kullanımının farklı bir şablon türü oluşturmasıdır. Örneğin take()
algoritmasının döndürdüğü özel aralık nesnesinin türü take()
'e gönderilen aralık türüne göre değişir:
writeln(typeof([11, 22].tersi.take(1)).stringof); writeln(typeof(FibonacciSerisi().take(1)).stringof);
Çıktısı:
Take!(Tersi!(int[])) Take!(FibonacciSerisi)
Bunun doğal sonucu, farklı türlere sahip olan aralık nesnelerinin uyumsuz oldukları için birbirlerine atanamamalarıdır. Bu uyumsuzluk iki InputRange
nesnesi arasında daha açık olarak da gösterilebilir:
auto aralık = [11, 22].tersi; // ... sonraki bir zamanda ... aralık = FibonacciSerisi(); // ← derleme HATASI
Bekleneceği gibi, derleme hatası FibonacciSerisi
türünün Tersi!(int[])
türüne otomatik olarak dönüştürülemeyeceğini bildirir:
Error: cannot implicitly convert expression (FibonacciSerisi(0,1)) of type FibonacciSerisi to Tersi!(int[])
Buna rağmen, her ne kadar türleri uyumsuz olsalar da aslında her ikisi de int
aralığı olan bu aralık nesnelerinin birbirlerinin yerine kullanılabilmelerini bekleyebiliriz. Çünkü kullanım açısından bakıldığında, bütün işleri int
türünden elemanlara eriştirmek olduğundan, o elemanların hangi düzenek yoluyla üretildikleri veya eriştirildikleri önemli olmamalıdır.
Phobos, bu sorunu inputRangeObject()
ve outputRangeObject()
işlevleriyle giderir. inputRangeObject()
, aralıkları belirli türden elemanlara sahip belirli çeşit aralık tanımıyla kullandırmaya yarar. Aralık nesnelerini türlerinden bağımsız olarak, örneğin elemanları int
olan InputRange
aralığı genel tanımı ile kullanabiliriz.
inputRangeObject()
bütün erişim aralıklarını destekleyecek kadar esnektir. Bu yüzden nesnelerin tanımı auto
ile yapılamaz; aralık nesnesinin nasıl kullanılacağının açıkça belirtilmesi gerekir:
// "int giriş aralığı" anlamında InputRange!int aralık = [11, 22].tersi.inputRangeObject; // ... sonraki bir zamanda ... // Bu atama artık derlenir aralık = FibonacciSerisi().inputRangeObject;
inputRangeObject()
'in döndürdüğü nesnelerin ikisi de InputRange!int
olarak kullanılabilmektedir.
Aralığın örneğin int
elemanlı bir ForwardRange
olarak kullanılacağı durumda ise açıkça ForwardRange!int
yazmak gerekir:
ForwardRange!int aralık = [11, 22].tersi.inputRangeObject; auto kopyası = aralık.save; aralık = FibonacciSerisi().inputRangeObject; writeln(aralık.save.take(10));
Nesnelerin ForwardRange
olarak kullanılabildiklerini göstermek için save()
işlevlerini çağırarak kullandım.
outputRangeObject()
de OutputRange
aralıkları ile kullanılır ve onları belirli tür elemanlarla kullanılabilen OutputRange
aralığı genel tanımına uydurur.
Özet
std.range
modülü şablon kısıtlamalarında yararlı olan bazı şablonlar içerir.std.range
modülündeki şablonlar, tanımladığımız aralıkların başka aralıkların yeteneklerinin el verdiği ölçüde yetenekli olabilmelerini sağlarlar.inputRangeObject()
veoutputRangeObject()
, farklı türden aralık nesnelerinin elemanları belirli türden olan belirli çeşitten aralık genel tanımına uymalarını sağlarlar.