47

I'm relatively new to C++, still trying to get a hang of the syntax. I've been taking a look at a few operator overloading examples, most recently smart pointer implementations. Here's a really generic example I'm looking at:

template < typename T > class SP
{
    private:
    T*    pData; // Generic pointer to be stored
    public:
    SP(T* pValue) : pData(pValue)
    {
    }
    ~SP()
    {
        delete pData;
    }

    T& operator* ()
    {
        return *pData;
    }

    T* operator-> ()
    {
        return pData;
    }
};

When overloading the dereference operator why is the type T&? Similarly, when overloading the structure dereference why is the type T*?

user3273777
  • 471
  • 1
  • 4
  • 3
  • 13
    `*p = 2` is often desired syntax. It would be a shame if it didn't do anything. – chris Feb 05 '14 at 05:41
  • 2
    People usually at least attempt to forge consistency with there overloads. C++11 5.3.1p1 [expr.unary.op] lays out a `*` operator for pointer-types, and its not uncommon to mimic that (including overloads for `const` if you want to run the full gambit). – WhozCraig Feb 05 '14 at 05:49

5 Answers5

68

The dereference operator (*) overload works like any other operator overload. If you want to be able to modify the dereferenced value, you need to return a non-const reference. This way *sp = value will actually modify the value pointed to by sp.pData and not a temporary value generated by the compiler.

The structure dereference operator (->) overload is a special case of operator overloading. The operator is actually invoked in a loop until a real pointer is returned, and then that real pointer is dereferenced. I guess this was just the only way they could think of to implement it and it turned out a bit hackish. It has some interesting properties, though. Suppose you had the following classes:

struct A {
    int foo, bar;
};

struct B {
    A a;
    A *operator->() { return &a; }
};

struct C {
    B b;
    B operator->() { return b; }
};

struct D {
    C c;
    C operator->() { return c; }
};

If you had an object d of type D, calling d->bar would first call D::operator->(), then C::operator->(), and then B::operator->(), which finally returns a real pointer to struct A, and its bar member is dereferenced in the normal manner. Note that in the following:

struct E1 {
    int foo, bar;
    E1 operator->() { return *this; }
};

Calling e->bar, where e is of type E1, produces an infinite loop. If you wanted to actually dereference e.bar, you would need to do this:

struct E2 {
    int foo, bar;
    E2 *operator->() { return this; }
};

To summarize:

  1. When overloading the dereference operator, the type should be T& because that is necessary to modify the value pointed to by pData.
  2. When overloading the structure dereference, the type should be T* because this operator is a special case and that is just how it works.
Matt
  • 21,026
  • 18
  • 63
  • 115
13

The purpose of dereference operator is to dereference a pointer and returns the object reference. Hence it must return the reference. That is why it is T&.

The purpose of referring operator (->) is to return a pointer and hence T* is returned.

Saminathan S
  • 325
  • 2
  • 11
  • 7
    I think this question stems from the idea that "the purpose of operator-> is to return a pointer" is counter-intuitive. The confusion is that: "(*x)." and "x->" both ultimately dereference x and access a member of the dereferenced pointer. So the fact that one operator "is meant to" return a pointer and the other a reference isn't immediately obvious. – johnb003 Nov 03 '17 at 17:53
  • and it's so easy to understand [similar concept](https://doc.rust-lang.org/std/ops/trait.Deref.html) in Rust. – vikram2784 Feb 26 '22 at 04:33
2

It is because pointer contains an address of a variable referencing it will give a reference (or to say a lvalue refrence) to the address it has stored. e.g. int x; int *p; p=&x; now x and *p can be used interchangeably. if you do x =4; or *p = 4; both will have same result. *p works as a reference to x just like a normal reference int& t = x; will work.

Next thing structure dereference operator. This operator gives you the access of member variables through a pointer to object of a class. In above example the member is T* pData; so using this operator will give access to pData and pData is T* so the return type is T*. if pData in above example would have been T then the return type would have been T.

luck
  • 165
  • 1
  • 10
  • 1
    I interpret the added question "_Why is C++ designed in such way that operator* and operator-> are different in return types?_" like "Why does the `operator->()` overload _also_ add a built-in dereferencing step if used via the built-in `->` operator - when the overload could just return `T&` directly _without_ adding any step afterwards (except adding an implied `.` between _object_ and a mandatory _member_)". – Ted Lyngmo Nov 01 '19 at 15:43
1

Overloaded operators are just functions

When overloading operaters you are free to return anything basicly (just like any other function), with the noteable exception of member of pointer -> being somewhat special in this way.

In the example you show, a smart pointer, you aim to mimick the syntax of a pointer (pointer semantic). Then for syntax like:

*p = 2;

The indirection operator* return a non-const reference to object and we are able to modify through operator*. In effect, usually used for proxy type classes in the same way and it is a sort of a convention - but you could in theory return whatever.

The member of pointer operator -> is a bit tricky. See this answer for an explanation.

darune
  • 10,480
  • 2
  • 24
  • 62
0

We are making smart pointer and our aim is to access member of class/structure using any of this T& (reference) or T* (pointer) with flexibility.

for T* operator-> ():

T* operator-> ()
{
  return pData;
}
arrow operator (->) is a shorthand for (*p) and hence we return *pData which is of   type T*. 
here we are Overloding arrow operator so that members of T can be accessed like a pointer.

for T& operator* ():

T& operator* ()
{
  return *pData;
}
    * is "value at address" operator, here we are Overloding * (pointer) operator so 
    that members can be accessed  through a reference (T&).

We can change the behavior of the operator with operator overloading but its not recommended. overloaded operator behavior should be as close as its basic functionality.

Pankaj
  • 401
  • 1
  • 6
  • 16