Using "nil" to denote "nothing left" is an implementation detail. C++
has a different implementation, usually .end().
STL iterators are hardly delicate. I often find myself wishing other
languages supported C++-style iterators.
As an example of how to turn a nil-terminated list algorithm into a
..end()-terminated list algorithm, I submit a (reimplementation) of the
classic merge algorithm, compared to an SML/NJ version.
===== SML/NJ merge algorithm =====
fun merge(x::xs, y::ys) = if (x < y) then x::merge(xs, y::ys) else
y::merge(x::xs, ys) |
merge(nil, y::ys) = y::ys |
merge(x::xs, nil) = x::xs |
merge(nil, nil) = nil;
===== end SML/NJ =====
===== Full C++ for a merge example =====
#include <iostream>
#include <iterator>
#include <algorithm>
#include <vector>
#include <list>
using std::cout;
using std::endl;
using std::copy;
using std:

stream_iterator;
using std::vector;
using std::list;
using std::back_inserter;
template <typename T1, typename T2, typename T3>
void my_merge(T1 begin1, T1 end1, T2 begin2, T2 end2, T3 copy_to)
{
while (begin1 != end1 and begin2 != end2) {
if (*begin1 < *begin2)
*copy_to++ = *begin1++;
else
*copy_to++ = *begin2++;
}
if (begin1 != end1)
copy(begin1, end1, copy_to);
if (begin2 != end2)
copy(begin2, end2, copy_to);
}
int main(void)
{
const int FOO[] = { 0, 2, 4, 6 };
const int BAR[] = { 1, 3, 5, 7 };
vector<int> foo(FOO, FOO + 4);
list<int> bar(BAR, BAR + 4);
vector<int> result;
my_merge(foo.begin(), foo.end(), bar.begin(), bar.end(),
back_inserter(result));
copy(result.begin(), result.end(), ostream_iterator<int>(cout, "\t"));
cout << endl;
return 0;
}
===== end C++ =====
.... As you can tell, it's the same algorithm in both instances. The
differences are purely in implementation, not in conception. That's
good news for you, because implementation differences are almost always
surmountable. Conceptual differences are much, much harder.