resim: Hunters in the snow (Pieter Bruegel)

std::next, std::prev

Necati Ergin

--

C++11 standartları ile kapatılan eksikliklerden biri de next ve prev fonksiyon şablonları. İteratör’ler üzerinde çalışan bu fonksiyonarı anlayabilmek için önce ihtiyaç noktasını görmemiz gerekiyor. Elimizde aşağıdaki gibi bir kap olsun:

std::vector<int> ivec(100);

Bu kabın tuttuğu tüm öğeler üstünde bir algoritmayı koşturmak için begin ve end fonksiyonlarından alacağımız iteratörleri kullanmamız gerekir, değil mi?

std::for_each(ivec.begin(), ivec.end(), [](int &r){r * = 2;});

Yukarıdaki deyimde standart for_each algoritmasıyla ivec isimli std::vector nesnesinde tutulan tamsayıların hepsini iki katına çıkartmış oluyoruz.

Peki ya, vector'ün tüm öğeleri için değil de belirli bir aralığı üstünde bir algoritmayı koşturmak istersek ne yapacağız? Bu amaçla iteratör aritmetiğinden faydalanabiliriz. Örneğin ilk ve son öğe haricindeki tüm öğeleri içeren aralık üstünde işlem yapmak istediğimizi düşünelim:

std::for_each(ivec.begin() + 1, ivec.end() - 1, [](int &r){r * = 2;});

Eğer kabımız bir vector ise bu işlemde bir sorun yok. Çünkü vector kabının iteratörleri tamsayı ile toplama arayüzünü de sunan “random access iterator” kategorisinde.

ivec.begin() + n

gibi bir ifadeyle ivec kabında tutulan n indisli öğenin konumunu tutan bir iteratör elde edebiliyoruz. Peki kabımız bir std::list nesnesi olsaydı?

list<int>ilist(100);
///
std::for_each(ilist.begin() + 1, ilist.end() - 1,
[](int &r){r * = 2;}); //geçersiz

kodumuz geçerli değil. Çünkü “bidirectional iterator” kategorisinde olan list sınıfının iteratörlerinin arayüzünde toplama işlemi yok. Şöyle bir kod yazabiliriz:

#include <list>
#include <algorithm>
#include <iostream>
int main()
{
std::list<int> ilist{ 5, 56, 7, 48, 11, 23, 17, 39 };
//
auto iter1 = ilist.begin();
++iter1;
auto iter2 = ilist.end();
--iter2;
for_each(iter1, iter2, [](int &x) {x *= 2; });
for (int i : ilist)
std::cout << i << " ";
}

<iterator> başlık dosyasında bulunan next ve prev fonksiyonları bu tür durumlarda işimizi bir hayli kolaylaştırıyor. Şimdi her iki fonksiyonu da inceleyelim:

template <class ForwardIterator>
ForwardIterator next(ForwardIterator iter,
typename iterator_traits<ForwardIterator>::difference_type n = 1);

Fonksiyonumuz birinci parametresi bir iteratör istiyor. Fonksiyonun değerle çağrıldığına (call by value) dikkat edin. Fonksiyonun ikinci parametresine belirlenen iteratör konumundan kaç sonraki konumu elde etmek istiyorsak o tam sayıyı geçiyoruz. İkinci parametrenin varsayılan argüman olarak 1 değerini aldığını görüyorsunuz. Yani ikinci parametreye bir arguman gönderilmez ise fonksiyondan geri dönüş değeri olarak bir sonraki öğenin konumunu tutan bir iteratör elde ediyoruz. iter, en az “forward iteratör” arayüzüne sahip bir adımlayıcı olmak üzere

next(iter)

ifadesi ile iter konumundan bir sonraki öğenin konumunu elde ediyoruz.

next(iter, n)

ifadesi ile iter konumundan n sonraki öğenin konumunu elde ediyoruz.
next fonksiyon şablonunun gerçekleştirimi aşağıdaki gibi olabilir:

template<class ForwardIt>
ForwardIt next(ForwardIt it,
typename std::iterator_traits<ForwardIt>::difference_type n = 1)
{
std::advance(it, n);
return it;
}

Standart advance fonksiyonununreferans yoluyla aldığı iteratörü n pozisyon ilerlettiğini hatırlayalım. Eğer n negatif bir tam sayı ise fonksiyona gönderilen adımlayıcı n pozisyon eksiltiliyor.

Gelelim prev fonksiyonuna. Bu fonksiyon ile bir iteratör konumundan n önceki konumu elde ediyoruz:

template<class BidIter>
BidIter prev(
BidIter it,
typename std::iterator_traits<BidirIt>::difference_type n = 1);

İteratörler üzerinde eksiltme işlemi yapabilmek için iteratörün en az “bidirectonal iterator” kategorisinde olması gerekiyor. iter, “bidirectonal iterator” arayüzüne sahip bir adımlayıcı olmak üzere

prev(iter)

ifadesi ile iter konumundan bir önceki öğenin konumunu elde ediyoruz.

prev(iter, n)

ifadesi ile iter konumundan n önceki öğenin konumunu elde ediyoruz.

next ve prev fonksiyonlarını kullanarak bir kap içinde tutulan öğelerin herhangi bir aralığı üstünde işlem yapabiliriz. Daha önce yazdığımız geçersiz koda geri dönelim:

#include <list>
#include <algorithm>
#include <iterator>
#include <iostream>
int main()
{
std::list<int> ilist{5, 56, 7, 48, 11, 23, 37, 49};
for_each(next(ilist.begin()), prev(ilist.end()),
[](int &r){r *= 2; });

for (int i : ilist)
cout << i << " ";
}

Yukarıdaki kodda

next(ilist.begin())

ifadesi ile ilist kabında tutulan ikinci öğenin konumunu

prev(ilist.end())

ifadesi ile kapta tutulan son öğenin konumunu elde ediyoruz. for_each algoritmasıyla ilist kabındaki ilk ve son öğe haricindeki tüm öğeleri 2 katına çıkartmış olduk.

next ve prev fonksiyonlarına geçersiz bir iteratör konumunun geçilmesi tanımsız davranış oluşturuyor. Eksiltme ya da artırma sonucu geçersiz bir konumun elde edilmesi yine tanımsız davranış. Yani her iki durumda da bir hata nesnesi (exception) gönderilmediğini hatırlatalım.

--

--