November, 1994 - Vol. 1 No. 11
In the article C++ Programming - An introduction to iterator classes in the July 1994 issue of Borland
C++ Developer's Journal, you used a programming
construct that worksbut I can't figure out how
it does so. In the example code, you included the lines
while(FwdIterator) FwdIterator() = (value++) * 2;
The first line doesn't seem to use valid syntax. Apparently,
the second line uses the overloaded function call operator, but
how is the compiler able to accept the object FwdIterator
as an argument to the while() expression?
Steve Robinson
Rockville, Maryland
Steve, in the forwardIterator class, we defined a special function called a conversion operator to convert forwardIterator objects to integer values. The primary purpose of this conversion is to let us test the iterator objects for an out-of-bounds condition when they iterate past the end of the array.
Unfortunately, in the article you mentioned, we mistakenly called
the conversion operator a conversion constructor. This is a common
mistake, since those functions do similar things. To clarify this
issue, let's review the three ways you can specify when
the compiler is able to convert a class object to another type.
The first way you can specify a valid conversion from one type to another is by using inheritance. By declaring an inheritance relationship between two classes, the derived class can have access to the copy constructor of the base class and use it to copy the base class subobject.
For example, consider the following class:
class RichDaddy { public: RichDaddy(RichDaddy& rd); };
If you derive a new class by adding
class RichKid : public RichDaddy { // details };
you'll be able to use a RichKid object any place you can use a RichDaddy object. This is because the compiler will be able to use the RichDaddy class's copy constructor to copy the RichDaddy subobject inside any RichKid object.
In addition, this is the only way for you to tell the compiler
it can implicitly (without a cast) convert a pointer to a base
type into a pointer to a derived type. However, this technique
is limitedyou can't use it to convert a class
object to a fundamental type (int, char, float,
and so on) or vice-versa, because you can't derive new
classes from or redefine the fundamental types.
The second technique you can use to allow conversion from one type to another is to define a conversion constructor. A conversion constructor simply accepts a single parameter of the desired type.
For example, if you add the conversion constructor
RichDaddy(int) { //details }
to the RichDaddy class, you can then supply an integer value in many places where the compiler expects a RichDaddy object.
Once again, you can use this technique only to create objects
of user-defined classes. However, you can create user defined
objects from a fundamental type.
The third kind of conversion you can define is called a conversion operator (some people call this a conversion function). Conversion operators are by far the most flexible way for you to change an object from a user-defined object into some other data type.
To define a conversion operator, you simply declare a member function
using
operator type ();
where type can be any one of the following:
You'll never call a conversion operator directly. Instead,
you can simply use an object from a user-defined class in places
that would expect an object of the conversion type type.
If the compiler finds an object in an expression where the compiler
expects a different data type, the compiler will automatically
call the appropriate conversion operator, if you've defined
one.
In the example program (ITERATOR.CPP) from the article you mentioned,
creating the operator int() conversion operator tells
the compiler to turn the statement
while(FwdIterator)
into
while(FwdIterator.operator int())
because the while() expression expects an integral expression in the parentheses. The return value from the operator int() conversion operator function body will be 1 if the iterator is pointing to a location within the bounds of the array. If the iterator goes past the end of the array, the conversion operator calls reset() to reset the index to point to the beginning of the array, and then returns 0 to indicate the out-of-bounds condition.
Inside the parentheses of the while() expression, you must either provide an expression that reduces to an integral value or specify some form of conversion to an integral type. Since the conversion operator is available, the compiler will call it and use its return value as the argument for the while() expression.
Later in the main() function, we use the same technique
to test both the forward and reverse iterators for an out-of-bounds
condition, by writing
while(FwdIterator && RevIterator) { cout << FwdIterator() << " "; cout << RevIterator() << endl; }
These lines call the conversion operator for the FwdIterator
and RevIterator objects and retrieve their integral out-of-bounds
conditions (since the && operator expects integral
operands). Then, the compiler can perform the logical AND operation
on the resulting integer values.
As a general rule, use inheritance or conversion constructors
to convert objects from one user-defined type to another user-defined
type. To convert a fundamental type to a user-defined type, use
a conversion constructor. Use conversion operators when you need
to convert a user-defined type to a fundamental type or to any
type of pointer.
Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.