[comp.lang.c++] Class members flexible enough?

wicklund@intellistor.com (Tom Wicklund) (02/23/91)

I've been looking at a few implementations of class "complex" recently
and wonder current C++ class members are sufficient.

A "simplistic" implementation of complex would look something like:

class complex {
  protected:
    double re;
    double im;

  public:
    // constructors ...
    complex operator+(complex c);
    complex operator-(complex c);
	    .
	    .
	    .
};

The operators on complex values (+, -, *, /, etc) are defined as
member functions since they operate on the data type "complex".

However, in the implementations of complex I've seen I find that
member functions are not used, and it appears intentionally avoided
whenever possible.

The ATT C++ library (as documented in the C++ 2.0 library reference
manual, I don't have access to cfront or the actual library) they
define all complex operators as friend functions:

    friend complex operator+(complex c1, complex c2);
    friend complex operator-(complex c1, complex c2);


The GNU g++ library implements complex operators completely outside of
the class (they are not even friend functions) via:

Complex /* const */ operator + (const Complex& x, const Complex& y);
Complex /* const */ operator + (const Complex& x, double y);
Complex /* const */ operator + (double x, const Complex& y);

Complex /* const */ operator - (const Complex& x, const Complex& y);
Complex /* const */ operator - (const Complex& x, double y);
Complex /* const */ operator - (double x, const Complex& y);


These implementations bring up the questions:

1.  Why aren't member functions used?  In the ATT case my guess is
    that the friend implementation allows automatic int/float
    conversion to complex on either side of the operator.  A member
    function "operator +" won't convert the left hand argument
    automatically.  In the GNU case it looks like somebody decided to
    implement these functions independent of class representation
    (the implementation is in terms of public functions and a
    constructor).

2.  If member functions aren't powerful enough to implement a class
    like complex, is the language lacking something?  The whole
    concept of friend functions seems to point to missing language
    facilities.

jbuck@galileo.berkeley.edu (Joe Buck) (02/27/91)

In article <1991Feb22.190313.4936@intellistor.com>, wicklund@intellistor.com (Tom Wicklund) writes:
|> I've been looking at a few implementations of class "complex" recently
|> and wonder current C++ class members are sufficient.

They are.  Your example is a poor design of the "complex" class.  In
fact, friend functions are not needed at all.  I recommend that you
define class Complex something like this:

class complex {
private:
	double re;
	double im;
public:
	// constructors
	complex() : re(0), im(0) {}
	complex(double r) : re(r), im(0) {}
	complex(double r, double i) : re(r), im(i) {}
	complex(const complex& c) : re(c.re), im(c.im) {}

	// assignment operators
	complex& operator=(const complex& c) {
		re = c.re; im = c.im;
		return *this;
	}
	complex& operator+=(const complex& c) {
		re += c.re; im += c.im;
		return *this;
	}
	// -=, *=, /= defined similarly
};

inline complex operator+ (const complex& a, const complex& b) {
	complex tmp(a);
	return tmp += b;
}

// -, *, / defined similarly.

NOTICE: operator+ is NOT a friend.  It only accesses public
operations of class complex.  An approach like this is used
in the Gnu library, and is advocated by Andrew Koenig in his
tutorials.

Notice the structure of the operator+ function above.  Notice
that it expresses the logical relation between the + operator
and the += operator in a very clean way.  In fact, it could be
implemented as a template, once these are added to the language.
On that glorious day one can write

template <class T> inline T operator+ (const T& a, const T& b) {
	T tmp(a);
	return tmp += b;
}

and instantly, any class with a += operator has a corresponding
+ operator (see chapter 14 of E&S for a discussion of templates).


|> These implementations bring up the questions:
|> 
|> 1.  Why aren't member functions used?  In the ATT case my guess is
|>     that the friend implementation allows automatic int/float
|>     conversion to complex on either side of the operator.  A member
|>     function "operator +" won't convert the left hand argument
|>     automatically.  In the GNU case it looks like somebody decided to
|>     implement these functions independent of class representation
|>     (the implementation is in terms of public functions and a
|>     constructor).

Your guess is correct; in both cases a symmetrical + operator results
(it can add int, double, float, complex in any combination).

The GNU case is doing it the right way.  Member functions of an object
are functions that operate on that object.  + does not operate on
a single object, it takes two arguments and produces an output that
is a function of both and changes neither.  It can be cleanly defined
in terms of the += primitive.

|> 2.  If member functions aren't powerful enough to implement a class
|>     like complex, is the language lacking something?  The whole
|>     concept of friend functions seems to point to missing language
|>     facilities.

As the GNU case shows, friends are not needed at all.  Some people think
everything should be done with member functions.  This is misguided,
in my view.  Member functions define the interface of a class.  More
complex operations that can be defined efficiently using class primitives
shouldn't be member functions, in my view; they should be what Grady
Booch calls "free subprograms".


--
Joe Buck
jbuck@galileo.berkeley.edu	 {uunet,ucbvax}!galileo.berkeley.edu!jbuck