std::next ve std::prev

Necati Ergin
3 min readJul 23, 2021

next ve prev isimli fonksiyon şablonları standart kütüphaneye C++11 standartları ile eklendi. İteratörler üzerinde çalışan bu fonksiyonlara neden ihtiyaç duyuluyor? Elimizde aşağıdaki gibi bir kap (container) 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 işlevlerinden alacağımız iteratörleri kullanmamız gerekir, değil mi?

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

Yukarıdaki deyimde standart for_each algoritması ileivec isimli 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 haricindeki tüm öğeleri içeren aralık üstünde işlem yapmak istediğimizi düşünelim:

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 acces 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 sstd::list nesnesi olsaydı?

std::list<int>ilist(100);
///
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 işlevleri bu tür durumlarda işimizi bir hayli kolaylaştırıyor. Şimdi her iki işlevi de inceleyelim:

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

İşlevimiz birinci parametresi bir iteratör istiyor. İşlevin değerle çağrıldığına (call by value) dikkat edin. İşlevin 2. parametresine belirlenen iteratör konumundan kaç sonraki konumu elde etmek istiyorsak o tamsayı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 işlevden geri dönüş değeri olarak bir sonraki öğenin konumunu tutan bir iteratör elde ediyoruz. iter, "forward iterator" arayüzüne sahip bir iteratör 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 işlev ş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 işlevinin referans yoluyla aldığı iteratörün pozisyon ilerlettiğini hatırlayalım. Eğer n negatif bir tamsayı ise işleve gönderilen iteratörn pozisyon eksiltiliyor.

Gelelim prev işlevine. Bu işlev 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 "bidirectional iterator" kategorisinde olması gerekiyor. iter, "bidirectional iterator" arayüzüne sahip bir iteratör 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 işlevlerini 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)
std::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 listesindeki ilk ve son öğe haricindeki tüm öğeleri 2 katına çıkartmış olduk.

next ve prev işlevlerine geçersiz bir iteratör konumunun geçilmesi tanımsız davranış. Eksiltme ya da artırma sonucunun geçersiz bir konum olması yine tanımsız davranış. Yani her iki durumda da bir hata nesnesi (exception) gönderilmiyor.

--

--