Hello all:

I have read that the purpose of const member functions is to specify which functions may be used on const objects. This makes sense.

An example is given in my text and I don't understand it. If anyone could help, I would be very grateful.

Here is the example:
Using const in function declarations can reduce errors without compromising safety or efficiency. For example, recall the declaration of operator* for the Wood class:

const Wood operator*(const Wood& lhs, const Wood& rhs);

Why should the return type be const? Otherwise, you could write code like this:
Wood a, b, c; (a * b) = c;

This would not be allowed by a built-in type. Making operator* return const disallows this for the Wood type too.

This is probably a dumb question, but I don't understand what is wrong with writing code like: Wood a, b, c; (a * b) = c;

Thanks

Can can anyone explain?

It's because of a concept commonly called "const-correctness". It's also a matter of creating code that makes sense both logically and mathematically.

Operators generate temporary objects while they are processing. If you put the proper debug/output code in your constructor(s)/destructor(s) you can actually see them being created and destroyed.

For example, if I take your example:

Wood a, b, c;
(a * b) = c;

This becomes:

WoodMultiplicationTemporary = c;

Now what do you do with the temporary once this operation is finished? How do you intend to access it later? You can't, it's lost. Not only that, it's logically and mathematically incorrect. How can you expect to solve an equation when the equation changes mid-calculation. There is no mathematically-legal algebraic manipulation that allows for this type of change in the middle of a calculation. For example, if you're given the equation (2 * 3) = 6, there is no way for it to legally become (3 * 3) = 6. That is a mathematically-false statement and as such is not solvable.

By making the return const, you help to make sure that your operations are not only logically correct, but mathematically legal. In addition, you also make that return value compatible with other, properly-written, const-correct code.

I don't understand what is wrong with writing code like: Wood a, b, c; (a * b) = c;

There's nothing wrong per se. It's generally frowned upon because when overloading operators, the general rule of thumb is "do as the ints do", or match the semantics of built-in types. If the same operators have different semantics on your custom type than they do on built-in types (or standard library types), you make understanding the code more difficult.

commented: Good rule of thumb. I'll have to make sure I remember that one. :) +5

It's because of a concept commonly called "const-correctness". It's also a matter of creating code that makes sense both logically and mathematically.

Operators generate temporary objects while they are processing. If you put the proper debug/output code in your constructor(s)/destructor(s) you can actually see them being created and destroyed.

For example, if I take your example:

Wood a, b, c;
(a * b) = c;

This becomes:

WoodMultiplicationTemporary = c;

Now what do you do with the temporary once this operation is finished? How do you intend to access it later? You can't, it's lost. Not only that, it's logically and mathematically incorrect. How can you expect to solve an equation when the equation changes mid-calculation. There is no mathematically-legal algebraic manipulation that allows for this type of change in the middle of a calculation. For example, if you're given the equation (2 * 3) = 6, there is no way for it to legally become (3 * 3) = 6. That is a mathematically-false statement and as such is not solvable.

By making the return const, you help to make sure that your operations are not only logically correct, but mathematically legal. In addition, you also make that return value compatible with other, properly-written, const-correct code.

Hi Fbody:

Thanks for your help.
Sorry, but I still don't quite understand... What do you mean when you say that an equation changes mid-calculation? I don't see how this would happen. If we have:

Wood operator*(const Wood& lhs, const Wood& rhs);
instead of:

const Wood operator*(const Wood& lhs, const Wood& rhs);

then wouldn't a, b, and c be fixed anyway? Sorry for being so dense... I just don't understand this. Thanks for your patience.

There's nothing wrong per se. It's generally frowned upon because when overloading operators, the general rule of thumb is "do as the ints do", or match the semantics of built-in types. If the same operators have different semantics on your custom type than they do on built-in types (or standard library types), you make understanding the code more difficult.

Hi Narue:

Thanks. I do understand this, but how would:

Wood operator*(const Wood& lhs, const Wood& rhs);

instead of:

const Wood operator*(const Wood& lhs, const Wood& rhs);

have different semantics that built-in types? Thanks for your patience.

Hi Fbody:

Thanks for your help.
Sorry, but I still don't quite understand... What do you mean when you say that an equation changes mid-calculation? I don't see how this would happen. If we have:

Wood operator*(const Wood& lhs, const Wood& rhs);
instead of:

const Wood operator*(const Wood& lhs, const Wood& rhs);

then wouldn't a, b, and c be fixed anyway? Sorry for being so dense... I just don't understand this. Thanks for your patience.

I think I need to explain something to you before I answer you. Variables are storage locations for data. There are exceptions (and ways of manipulating it), but in general when you use operators on variables/objects, the program's execution moves from left-to-right through the operators. As the execution moves through the operators, the data/information flows from right-to-left. For example, if you have 2 ints 'iA' and 'iB' that you want to multiply together, you would write iA * iB . When this executes in C++, the variables "iA" and "iB" are provided to the operator* function as arguments/parameters to the function. The function does what it needs to then returns a temporary value which you are now responsible for doing something with. Most commonly, this temporary value is stored to another variable using the assignment operator (operator=), but it's not unusual for it to be used in other ways as well.

Given that description, let's assume for now that "Wood" is a typedef for int.

typedef int Wood

Now, let's provide some declarations and example values:

Wood a = 2;
Wood b = 3;
Wood c = 0;

Given these numbers, lets trace the behavior of a, b, and c in your equation:

//original equation:
(a * b) = c;

//perform substitution(s):
(a * b) = c;
 |   |    |
 V   V    V
(2 * 3) = 0;    //<---REMEMBER: data moves through operators from right-to-left
                //    you should already be able to see forthcoming problems here

//operators execute from left to right, parentheses override as necessary:
(2 * 3) = 0;
   |      |     //execute multiplication operator
   V      V
 ( 6 )  = 0;    //result of multiplication is temporarily stored in memory
   |            //execute assignment operator
   V
 ( 0 )          //WHERE DID MY 6 GO?!?!?!?!

Notice how the 6 was overwritten by the 0? Additionally, you have no way of accessing the memory where the 0 is stored. The temporary variable the 0 is in now goes out of scope and is destroyed. You've now lost that data and there is no way to get it back.

If you make your return value const, the compiler catches this situation and doesn't allow you to make this mistake.

commented: Very patient; very detailed, complete answer +2
commented: Nice one +6

Thanks a lot Fbody!! I get it now. I really appreciate all the time you took.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.