Topic: Binop semantics in C++


Author: puccio@media-lab.media.mit.edu (Jim Puccio)
Date: 2 Feb 92 21:25:03 GMT
Raw View
As a comparative newcomer to C++, I am now going through a process no doubt
very familiar to a lot of you - rethinking my most handy-dandy C-based ADTs
to take advantage of nifty C++ features like operator overloading and so on.

And, again, I'm sure like pretty much every C programmer who has made this
transition, I am dismayed by the constant construction and destruction of
temporaries that goes on.

I would like to use the way in which my *old* C-based Matrix class was
constructed to illustrate a point about how I think (at least in similar cases)
much of this needless construction/destruction may be avoided.

The way I used to do it was as follows:

In Matrix.h we would find stuff like:

    typedef struct Matrix * Matrix;

    struct Matrix
        {
            <innards>
        };

And a typical binary operator in Matrix.c would look like this:

    Matrix Matrix_<binop>
        (
            register Matrix out,
            register Matrix left,
            register Matrix right
        )
        {
            auto struct Matrix temp;
            register struct Matrix answer;

            if ((! out) && (out = Matrix_New (1)))
                return (NULL);

            answer = (((out == left) || (out == right)) ? (& temp) : out);

            <operate on answer>

            if (answer == (& temp))
                (* out) = temp;

            return (out);
        }

This allowed a number of modes of use:

1)  Matrix_<binop> (NULL, mat2, mat3);   // Allocates a new Matrix with answer
2)  Matrix_<binop> (mat1, mat1, mat2);   // mat1 <binop>= mat2
3)  Matrix_<binop> (mat1, mat2, mat3);   // mat1 = mat2 <binop> mat3;

Why am I bringing this up?  Well, as you can see from the final mode of use,
treating a binary arithmetic operator as ternary allows the operator direct
access to the temporary in whatever (and however deeply-nested) calling frame,
without paying the penalty of a chain of return-by-value constructor/destructor
activity.

My suggestion, then, is that the semantics of binary operator overloading be
widened to support sugared ternary access, eg:

friend Matrix & operator <binop>
    (
        Matrix & temporary,
        const Matrix & left,
        const Matrix & right
    );

With direct access to the temporary, it would be possible to write operators
as efficient as the old-fashioned C one above.

Now, you may reply, why not use <binop>= ?  Of course that is an option,
but this still requires the user of the class to explicitly maintain their own
temporaries, as had been the case in my old ADT illustrated above.  It is my
impression that the whole point of this overloading business is to make client
code as transparent as possible.  I don't want my clients to have to know that

    a = b * c;              is a lot more expensive than

    a = b; a *= c;          and should therefore be avoided in favor of the
                            more verbose version.

(Actually, semantic identity would be preserved with something more like

    (a = b), (a *= c);      since it could be embedded in an expression
                            context.)

Another possibility is to set things up so that the client says something like

    a.<opname> (b, c)       but that's really no better than my old C way.

As I see it, allowing binops to be defined as ternaries, with reference
access to the temporary in the calling frame would simplify things
syntactically, make C++ a more efficient language to use, and need not break
old code, since it could be left such that writing an operator definition with
"old-fashioned" binop access would do the traditional C++ return-by-value
dance.

Is this suggestion totally out of line?  Is there something I just don't get?

-- Jim Puccio [puccio@media-lab.media.mit.edu]




Author: puccio@media-lab.media.mit.edu (Jim Puccio)
Date: 2 Feb 92 21:36:16 GMT
Raw View
Woops!

In the sample old-fashioned C code, answer should be declared

    register Matrix answer;

Typing on autopilot, I guess ...




Author: sch@mitre.org (Stu Schaffner)
Date: Mon, 3 Feb 1992 15:41:03 GMT
Raw View
In article <PUCCIO.92Feb2162503@media-lab.media.mit.edu>, puccio@media-lab.media.mit.edu (Jim Puccio) writes:
>
> ...
> And, again, I'm sure like pretty much every C programmer who has made this
> transition, I am dismayed by the constant construction and destruction of
> temporaries that goes on.
> ...

First, you may want to rethink whether this level of inefficiency
has any significant effect on overall program response when said
program is run on today's multi-MIPS machines.  If there are still
problem cases, you will find a good description of how C++'ers do
it in
  James O. Coplien, "Advanced C++ programming styles and idioms"
  Addison Wesley, 1992
  ISBN 0-201-54855-0

Stu Schaffner (not speaking for the)
MITRE Corp.
sch@mitre.org