Reputation: 313
For the sake of practice, I decided to implement a list container, with an associated Iterator object. I havn't implemented iterators before, but I used the pattern frequently. The iterator object looks as follows:
template <typename DataType>
struct FNode
{
DataType Data;
FNode* Next;
FNode* Prev;
FNode(DataType _data, FNode* _next, FNode* _prev)
: Data(_data)
, Next(_next)
, Prev(_prev)
{
}
};
template <typename DataType>
struct TListIterator
{
TListIterator(FNode<DataType>* _Element)
: Element(_Element)
{
}
FNode<DataType>* Element;
// Some operators overload ....
// ....
DataType operator ->()
{
return Element->Data;
}
So in essence, I want an iterator to retain a pointer of a node in my list/container. My issue arises when accessing data via the iterator. I want to be able to simply use the arrow operator to directly access the DataType variable contained in the node.
The implementation above works fine if my template argument is a pointer. However, if I decide to use a list that stores a non-pointer data type, the arrow operator fails because I am no longer returning a pointer type. Needless to say, if I change the operator overload to return a pointer like this:
DataType* operator ->()
{
return &Element->Data;
}
It works for non-pointer types, but fails for pointer types.
What key idea am I misunderstanding here - should my iterator object not deal with node pointers? Or is there a way to overload another operator to handle both pointer and non-pointer types conveniently?
I also notice I encounter similar problems generally when writing template code. Most of the time, I would want to process the data differently depending on whether its a pointer or not.
EDIT Here are some examples
Given a struct:
struct FScanHit
{
unsigned long TimeStamp;
uint16_t Distance;
uint8_t Angle;
};
Given a class:
class Object
{
public:
void Update();
}
Attempting to manage a list of FScanHit and a list of Object* :
TList<FScanHit> scans;
TList<Object*> objects;
// Add elements to either lists
// ....
// Access the actual data
TListIterator<FScanHit> scanIt = scans.Begin(); // Begin uses the TListIterator constructor shown above to create an iterator whose element is the head node of the list
TListIterator<Object*> objectsIt = objects.Begin();
objectsIt->Update(); // Works, because ->() returns a pointer type Object*
uint8_t dist = scanIt->Distance; // Error: result of 'operator->()' yields non-pointer result
Upvotes: 0
Views: 1377
Reputation: 119877
Exibit A:
template <class T> T foo() { ... }
foo<Object*>()->Update()
typechecks, and foo<Object>()->Update()
doesn't, because foo<Object>()
is not a pointer to a class type object — it is a class type object.
Exhibit B:
template <class T> T* foo() { ... }
Now foo<Object>()->Update()
typechecks, and foo<Object*>()->Update()
doesn't, because
foo<Object*>()
is not a pointer to a class type object again — it's a pointer to a pointer.
You have the exact same issue with your operator->()
.
Which version to use?
If you complement the second version (returning a pointer) with operator*()
, you can use objectsIt->Update()
or (*objectsIt)->Update()
, depending on whether you have a pointer or not. Hmm, that looks familiar. Doesn't objectsIt
have a syntax resembling that of a regular pointer? Well, this is exactly what an iterator should do!
Upvotes: 1