overload "operator->"

J

Jess

Hi,

I read about operator overloading and have a question regarding
"operator->()". If I have two classes like this:

struct A{
void f();
};

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

Then we can do:

B b;
b->f();

and A's f() is called.

However, the overloading of -> seems to say it's a unary operator, and
is prefixed (because of the ()). Therefore, to me, it looks like we
should use this operator like:
B::eek:perator->b,

which will give use a the pointer member in B. Then to access "f", I
think we need to do:

(B::eek:perator->b)->f(),

which is very different to b->f(). Can someone please tell me why
using b->f() gives me the f() of A?

Thanks,
Jess
 
I

Ian Collins

Jess said:
Hi,

I read about operator overloading and have a question regarding
"operator->()". If I have two classes like this:

struct A{
void f();
};

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

Then we can do:

B b;
b->f();

and A's f() is called.

However, the overloading of -> seems to say it's a unary operator, and
is prefixed (because of the ()). Therefore, to me, it looks like we
should use this operator like:
B::eek:perator->b,

No, it's shorthand for b.operator->().
which will give use a the pointer member in B. Then to access "f", I
think we need to do:

(B::eek:perator->b)->f(),
b.operator->()->f()
 
J

Jess

No, it's shorthand for b.operator->().


b.operator->()->f()

But the declaration A* operator->() shows "->" should be used as a
prefix of "b", isn't it? Using it as a prefix should give us "->b"...

Moreover, your expansion above seems to say we can expand "b" to
"b.operator->()", I really can't see where it's from....

Jess
 
I

Ian Collins

Jess said:
But the declaration A* operator->() shows "->" should be used as a
prefix of "b", isn't it? Using it as a prefix should give us "->b"...
How so? Operator -> is a unary *postfix* operator.

See section 11.10 of Stroustrup.
 
J

Jess

How so? Operator -> is a unary *postfix* operator.

I see, so we can't change its postfix property when overloading it.
My other two questions are:

a. if we use it as postfix, then why don't we overload it as:

A* operator->(void)

to show it has no right argument? Why do we have () in "A* operator-
()"? What does the "()" mean?

b. why is b->f() the shorthand of
(b.operator->()) -> f()

now there are two "->". I think in b->f(), "b->" should be expanded
to "b.operator->". Then b->f() is the shorthand of

(b.operator->)f()

(which obviously isn't a valid syntax...)

Thanks,
Jess
 
I

Ian Collins

Jess said:
I see, so we can't change its postfix property when overloading it.
My other two questions are:

a. if we use it as postfix, then why don't we overload it as:

A* operator->(void)
Why should we? Are you thinking of the prefix and postfix ++ and --
operators? Th use of (int) with this is required to disambiguate the
operator.
to show it has no right argument? Why do we have () in "A* operator-

b. why is b->f() the shorthand of
(b.operator->()) -> f()

now there are two "->". I think in b->f(), "b->" should be expanded
to "b.operator->". Then b->f() is the shorthand of
What? b.operator->() returns a pointer:

A* p = b.operator->();
a->f();
 
J

James Kanze

I see, so we can't change its postfix property when overloading it.

You can't change the syntax of C++ with operator overloading.
You can only extend it to support additional types.
My other two questions are:
a. if we use it as postfix, then why don't we overload it as:
A* operator->(void)
to show it has no right argument?

That's how we do overload it. Except that the void isn't
necessary. But it has nothing to do with whether the argument
is right or left. In general, when overloading operators, the
first operand of the function is the first operand of the
operator, and so on. If the overloaded operator is a member
(required for operator->), its first operand is this, so it has
one less formal parameter.
Why do we have () in "A* operator-

That you are declaring/defining a function. An overloaded
operator is always a function.
b. why is b->f() the shorthand of
(b.operator->()) -> f()

Because that's what the standard says.
now there are two "->". I think in b->f(), "b->" should be expanded
to "b.operator->".

The name of the function? That doesn't make any sense.
Then b->f() is the shorthand of

(which obviously isn't a valid syntax...)

No, because a user defined overload is always implemented as a
function, and when the operator is used, that function is
called.
 
S

Sylvester Hesp

Jess said:
I see, so we can't change its postfix property when overloading it.
My other two questions are:

a. if we use it as postfix, then why don't we overload it as:

A* operator->(void)

to show it has no right argument? Why do we have () in "A* operator-

You seem to assume that how operator overloading is expressed in any way
reflects the orientation (prefix/postfix/infix) of the operator itself.
Assumptions are the mother of all ****-ups :). Your assumption is incorrect,
it only reflects the arity of the operator. An operator with 1 parameter is
an unary operator (whether they are prefix or postfix), and an operator with
2 parameters is a binary operator. Keep in mind the 'this' pointer is an
implicit first parameter. The only two exceptions to this are the postfix ++
and -- operators. Since both the postfix and prefix variants are unary,
their function definitions would be the same and therefore ambiguous. That's
why an extra int parameter is used for the postfix ++ and -- operators to
distinguish them from the prefix ones.

The operator syntax is just like ordinary functions, where the function name
is followed by parameters contained in parantheses. The function-style name
for the -> operator is "operator ->". Just as you would do b.func(), you can
also do b.operator->(). Likewise, ++b is the same as b.operator++().

- Sylvester Hesp
 
J

Jess

What? b.operator->() returns a pointer:

A* p = b.operator->();
a->f();

Yes, from here, it shows "(b.operator->()) -> f()" is what we want,
and I couldn't understand why it's equivalent to "b->f()". But if it
is the standard (as James said), then I'll take it as a fact. :)

Thanks,
Jess
 
J

Jess

You can't change the syntax of C++ with operator overloading.
You can only extend it to support additional types.

By "extending", I guess I can't change a postfix to a prefix, or its
precedence? Can I change a member function to a non-member and vice
versa? Can I change a unary operator to a binary or a binary to a
unary? Also, when ">>" and "<<" are used by iostream, are then member
operators of iostream or non-member functions?
Because that's what the standard says.

I see, it's the standard, though it doesn't look very obvious why
standard says it. :) When "->" is used with a pointer "p->b()" is
equivalent to (*p).b(). which doesn't show how we can use "->" with
user-defined class types.

Thanks,
Jess
 
J

Jess

The operator syntax is just like ordinary functions, where the function name
is followed by parameters contained in parantheses. The function-style name
for the -> operator is "operator ->". Just as you would do b.func(), you can
also do b.operator->(). Likewise, ++b is the same as b.operator++().

Thanks a lot for the explanations!
Jess
 
?

=?ISO-8859-1?Q?Erik_Wikstr=F6m?=

By "extending", I guess I can't change a postfix to a prefix, or its
precedence?
Correct.

Can I change a member function to a non-member and vice
versa?

No, but for most(?) operators there's both a member and a non-member
version.
Can I change a unary operator to a binary or a binary to a
unary?
No.

Also, when ">>" and "<<" are used by iostream, are then member
operators of iostream or non-member functions?

Non-member, and that is how you should make the if you ever want to add
an operator<< to your class so that you can easily print it.

As far as I know the only operator that you have some freedom with is
the ()-operator, with which you can decide how many argument it takes,
all the others are quite fixed, in arity, precedence, postfix/prefix, etc.
I see, it's the standard, though it doesn't look very obvious why
standard says it. :) When "->" is used with a pointer "p->b()" is
equivalent to (*p).b(). which doesn't show how we can use "->" with
user-defined class types.

The idea is that you should be able to make a class that acts just like
a pointer, and that requires that it works like that, take a look at
this code and compare my Pointer-class with a real pointer:

#include <string>
#include <iostream>

template<class T>
class Pointer
{
T* ptr_;
public:
Pointer(T* p) : ptr_(p) {}
T* operator->() { return ptr_; }
T& operator*() { return *ptr_; }
};


int main()
{
std::cout << "Using Pointer:\n";
Pointer<std::string> P(new std::string());
*P = "Hello";
P->append(" World\n");
std::cout << *P;

std::cout << "Using a std::string pointer:\n";
std::string* p = new std::string();
*p = "Hello";
p->append(" World\n");
std::cout << *p;
}
 
J

Jess

Thanks a lot for the example!

Jess

No, but for most(?) operators there's both a member and a non-member
version.


Non-member, and that is how you should make the if you ever want to add
an operator<< to your class so that you can easily print it.

As far as I know the only operator that you have some freedom with is
the ()-operator, with which you can decide how many argument it takes,
all the others are quite fixed, in arity, precedence, postfix/prefix, etc.



The idea is that you should be able to make a class that acts just like
a pointer, and that requires that it works like that, take a look at
this code and compare my Pointer-class with a real pointer:

#include <string>
#include <iostream>

template<class T>
class Pointer
{
T* ptr_;
public:
Pointer(T* p) : ptr_(p) {}
T* operator->() { return ptr_; }
T& operator*() { return *ptr_; }

};

int main()
{
std::cout << "Using Pointer:\n";
Pointer<std::string> P(new std::string());
*P = "Hello";
P->append(" World\n");
std::cout << *P;

std::cout << "Using a std::string pointer:\n";
std::string* p = new std::string();
*p = "Hello";
p->append(" World\n");
std::cout << *p;

}
 
J

James Kanze

By "extending", I guess I can't change a postfix to a prefix, or its
precedence?

Right. And you cannot introduce new operators, either. (Say a
@ operator.) Basically, the compiler builds (or at least,
should be able to build) an expression tree without looking at
the types of the data. Only once it has the expression tree
does it look at the types, and map the operators onto either a
built-in operator or a user defined operator.
Can I change a member function to a non-member and vice
versa?

In general, no. Most operators, however, can be defined as
either one or the other.
Can I change a unary operator to a binary or a binary to a
unary?

No. On the other hand, C++ has a number of operator tokens
which can represent two different operators, one binary and one
unary. The choice of which is made *before* the compiler
evaluates whether to use a user defined operator or a built-in,
however.
Also, when ">>" and "<<" are used by iostream, are then member
operators of iostream or non-member functions?

Either (or in this case, both). >> and << are binary operators;
there are two ways of overloading a binary operator (with a few
exceptions): as a member function taking a single argument (with
the object the member function is called on as the first
argument to the operator, and the function argument as the
second), or as a free function taking two arguments.

The important thing to keep in mind here is that when the
compiler sees << or >>, it doesn't immediately consider
overloading of any sort. It just notes that it has a binary
operator of such and such precedance and binding, and continues
building the expression tree. Only when it has finished, and is
actually generating code, will it look at the possible
overloads, and do overload resolution over the set of built-in
operators, member function redefines, and free function
redefines.
I see, it's the standard, though it doesn't look very obvious why
standard says it. :)

True, but not for the reasons you seem to be saying. A priori,
looking at it, one would expect -> to be a binary operator.
It's not treaded as such, however, but as a unary operator. And
as a unary operator, if it is defined as a member function, the
operand is the object it is called on.

There aren't many examples of postfix unary operators to draw
on. (++ and -- are very special, as they can be either prefix
or postfix.) But this behavior is consistent with the idea that
a member function is called on an object, and that if the member
function is a user defined operator overload, the object the
function is called on is the first operand of the operator,
regardless of where the operand occurs relative to the operator.
When "->" is used with a pointer "p->b()" is
equivalent to (*p).b(). which doesn't show how we can use "->" with
user-defined class types.

This is true in general. For built in operators, a += b is the
equivalent of a = a + b, but there is no relationship if the
operators are defined by the user (unless the user decides to
create one---which is highly recommended).
 
J

James Kanze

On 2007-05-06 13:22, Jess wrote:
No, but for most(?) operators there's both a member and a non-member
version.

Most. The -> operator is an exception, as are =, [], () and
unary &.
Non-member, and that is how you should make the if you ever want to add
an operator<< to your class so that you can easily print it.

Both, or either, depending on your point of view. An operator<<
which takes an std::eek:stream as its first operand can be either a
non-member, or a member of std::eek:stream. The operator<< for
most of the built in types are members of std::eek:stream. For a
user defined type, of course, you're not allowed to add
functions to std::eek:stream, so you must define it as a non-member
function.
As far as I know the only operator that you have some freedom with is
the ()-operator, with which you can decide how many argument it takes,
all the others are quite fixed, in arity, precedence, postfix/prefix, etc.

Of course, the built-in () operator also accepts a variable
number of arguments, so this freedom isn't "extending" the
language in any way.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,772
Messages
2,569,593
Members
45,111
Latest member
VetaMcRae
Top