AAS.N
AAS.N

Reputation: 313

Overloading -> operator for pointer and non-pointer types

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

Answers (1)

n. m. could be an AI
n. m. could be an AI

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

Related Questions