23

If I use a default constructor for an iterator, how to check if it was assigned later on?

For pointers, I could do this :

int *p = NULL;
/// some code
if ( NULL == p ) {
  // do stuff
}

How do I do the above for iterators? Is it possible at all?

#include <iostream>
#include <list>

int main ()
{
    std::list<int>::iterator it;

  if ( NULL == it ) // this fails
  {
      std::cout<<"do stuff" << std::endl;
  }
}
BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • 1
    See also http://stackoverflow.com/questions/3395180/what-is-an-iterators-default-value – wimh Aug 05 '11 at 10:39
  • @Wimmel Cool. I am wondering how I didn't get that page. Anyway, it doesn't answer my question – BЈовић Aug 05 '11 at 11:13
  • 1
    The real solution is of course to always initialize your variables. Then you will know. :-) – Bo Persson Aug 05 '11 at 11:41
  • 1
    @Bo My idea was to use it like an uninitialized iterator. I thought it was possible. Something like boost::optional – BЈовић Aug 05 '11 at 12:17
  • 1
    VJo - It works for some iterators, where the default constructor makes it an end-iterator, but not in general. And not for containers. – Bo Persson Aug 05 '11 at 12:30

11 Answers11

22

I managed to find this in the current standard (c++03 ). 24.1 p 5 tells :

Just as a regular pointer to an array guarantees that there is a pointer value pointing past the last element of the array, so for any iterator type there is an iterator value that points past the last element of a corresponding container. These values are called past-the-end values. Values of an iterator i for which the expression *i is defined are called dereferenceable. The library never assumes that past-the-end values are dereferenceable. Iterators can also have singular values that are not associated with any container. [Example: After the declaration of an uninitialized pointer x (as with int* x;), x must always be assumed to have a singular value of a pointer. ] Results of most expressions are undefined for singular values; the only exception is an assignment of a non-singular value to an iterator that holds a singular value. In this case the singular value is overwritten the same way as any other value. Dereferenceable values are always non- singular.

(Emphasis mine)

So the answer is : no, it is not possible.

Enlico
  • 23,259
  • 6
  • 48
  • 102
BЈовић
  • 62,405
  • 41
  • 173
  • 273
4

Most iterators don't have any global special values in the same way that all pointers can be NULL. Typically, though, you'll be working with specific containers, and if you keep one iterator per container, then you can use end() as the sentinel value:

std::list<int> mylist;
std::list<int>::iterator it = mylist.end();

/* do stuff */

if (it == mylist.end()) { ... }

I'm not sure if insertion/deletion invalidates the end() iterator, though, so if you're planning on modifying your container, maybe save a copy of the original end, too:

std::list<int>::iterator end = mylist.end(), it = end;

if (it == end) { ... }

Though again I'm actually not sure if it's well-defined to compare two invalid iterators (in the event that the two do get invalidated).

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Specifically for the std::list, if you erase or insert elements anywhere in the list, all iterators and references to objects in the list remain valid. Except, of course, for the element you erased. For other containers, iterators are invalidated. – BЈовић Aug 05 '11 at 11:11
  • 1
    @VJo: But does that include `end()`, which isn't an iterator pointing to any value? – Kerrek SB Aug 05 '11 at 11:21
  • Yes, AFAIK except for vector and deque (and string), end will remain valid. – UncleBens Aug 05 '11 at 15:14
  • If the container has to reallocate space, then all iterators (including iterators that were set to the container's .end()!) become invalidated. – Doug Kavendek Nov 08 '11 at 19:18
2

Since there is no default value for iterators (like there is NULL for pointers), in situation where i need a common default value for a Object::iterator (before any actual Object has been created) I create a dummy static variable and use its ::end() as the default.

Update : This only works for Release, because in DEBUG (or with _HAS_ITERATOR_DEBUGGING=1) comparison operators check if both iterators point to the same object/container.

For example for vector<int> I would do :

class A
{
public :
    A() :  myIterator1(dummyVector.end()), myIterator2(dummyVector.end()) {}
    // needed iterators
    vector<int>::iterator myIterator1;
    vector<int>::iterator myIterator2;

    static const vector<int> dummyVector;
}

#define  IT_NULL A::dummyObject.end()

void maint() {
    A::dummyObject = vector<int>(); // initialize the Null iterator

    A a;
    if(a.myIterator1 == IT_NULL) cout << "Iterator not yet initialized";
}
code7amza
  • 181
  • 1
  • 8
2

This question has already been treated in Stackoverflow. The quintessence is that the default constructor initializes an iterator to a singular value, and the only addmissible operation on it is to assign it another iterator value. In particular it is not possible to query the value of such unitialized iterator. Therefore it is a good programming practice to initialize the iterator to a specific value of a specific container, which then can be tested for.

Community
  • 1
  • 1
Jiri Kriz
  • 9,192
  • 3
  • 29
  • 36
1

You can't. ll you can do is compare against list end

it != mylist.end();
cprogrammer
  • 5,503
  • 3
  • 36
  • 56
  • 3
    As far as I know, there's no general guarantee that an uninitialized iterator will equal the end iterator. – jalf Aug 05 '11 at 10:36
  • 1
    There is also no "the end iterator". Every object (instance!) has its own end. (With the exception of `istream`s.) – Kerrek SB Aug 05 '11 at 10:38
1

In C++, uninitialized local variables can have any value i.e it contains simply garbage. That implies, you cannot check it against some well-defined value, to determine if the variable is uninitialized or not.

Not only that if the variable is not initialized and you write this:

if ( NULL == it ) // this fails

then it invokes undefined behavior.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 1
    Not true for UDTs who have a default constructor, it's only true for POD types. – Puppy Aug 05 '11 at 10:47
  • 1
    @DeadMG: UDTs which have default constructor, are initialized by default. They shouldn't be called "uninitialized". – Nawaz Aug 05 '11 at 10:50
  • @DeadMG: What does your comment mean? What is "not true" for UDTs? – Nawaz Aug 05 '11 at 11:14
0

I used the following solution:

const MyList_t::const_iterator NullIterator(NULL);
const_iterator MyList_t::MyIterator;

Then a check is possible:

if (NullIterator != MyIterator) {}
0

Maybe you should always assign a predefined value, like NULL, after creating the iterator. Later you can easily check against NULL. This will make your code more portable, as you will not depend on what starting values the uninitialized variables take at the beginning.

vsz
  • 4,811
  • 7
  • 41
  • 78
0
if(std::list<int>::iterator() == it)

But I suspect... may it's possible, that a valid iterator could pass the comparison. Better to avoid these situations. If it's impossible store the iterator by a pointer.

 std::auto_ptr<std::list<int>::iterator> it;
kan
  • 28,279
  • 7
  • 71
  • 101
0

The best way to do this I can think of is something like

#include <utility>
#include <map>
#include <typeinfo>
#include <string>



namespace nulliterators {

typedef std::map<std::string, void*> nullcntT;
nullcntT nullcontainers;

template<class containerT>
typename containerT::iterator iterator() {
  containerT* newcnt = new containerT();
  std::string cnttypename = typeid(*newcnt).name();
  nullcntT::iterator i = nullcontainers.find(cnttypename);
  if (i==nullcontainers.end()) {
    nullcontainers.insert(make_pair(cnttypename, newcnt));
    return newcnt->end();
   }else{
    delete newcnt;
    return (static_cast<containerT*>(i->second))->end();
  }
}

}
template<class containerT>
typename containerT::iterator nulliterator() { return nulliterators::iterator<containerT>(); }


#include <list>
#include <iostream>


int main(){

  std::list<int>::iterator nullinitized = nulliterator< std::list<int> >();
  std::list<int> somelist;
  std::list<int>::iterator initialized = somelist.end();

  if (nullinitized == nulliterator< std::list<int> >())
    std::cout << "nullinitized == nulliterator< std::list<int> >()\n";  //true
   else
    std::cout << "nullinitized != nulliterator< std::list<int> >()\n";

  if (initialized == nulliterator< std::list<int> >())
    std::cout << "initialized == nulliterator< std::list<int> >()\n";
   else
    std::cout << "initialized != nulliterator< std::list<int> >()\n";  //true

  return 0;
}

but it's not exactly a safe solution (because it relies on the non-const global containers in nullcontainers).

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
0

As far as I know you must always initialize your iterators and the easiest way is to make them equal to 'container'.end() In certain cases it looks like working, we had some problems with code that worked with VC6 and stopped working with VC2010. Look at this example compiled with g++ where it works for the vector but not for the map:

# Test iterator init, compile with: g++ test-iterator.cpp -o test-iterator
#include <iostream>
#include <vector>
#include <map>

int main()
{
    std::vector<int> vec;
    std::vector<int>::iterator it;

    if (it != vec.end())
    {
            std::cout << "vector inside!" << std::endl;
    }
    else
    {
            std::cout << "vector outside!" << std::endl;
    }

    std::map<int, int> mp;
    std::map<int, int>::iterator itMap;

    if (itMap != mp.end())
    {
            std::cout << "map inside!" << std::endl;
    }
    else
    {
            std::cout << "map outside!" << std::endl;
    }

    return 0;
}
Pedro Ferreira
  • 852
  • 9
  • 23