[comp.lang.c++] named return values

dl@cmx.npac.syr.edu (Doug Lea) (08/08/89)

This is an argument for the `official' adoption in C++ of the notion
of named return values, as have been present in GNU g++ for the last
few versions.

Named return values are a g++ extension that gives a name to the
object returned by a function, in a way vaguely similar to named
return `variables' in Pascal and other languages, but with some
important C++-specific benefits.

To allow for some examples and discussion, first declare a 
very simple vector-like class X:

static const int Xsize=100000; // constant size, just for simplicity

class X
{
    int  p[Xsize];             // Xs are BIG objects

    void copy(const X& a)      { for (int i=0; i<Xsize; ++i) p[i] = a.p[i]; }
    void fill(int v)           { for (int i=0; i<Xsize; ++i) p[i] = v; }

public:

         X()                   {}
         X(const X& a)         { copy(a); }
         X(int v)              { fill(v); }
    
    void operator=(const X& a) { copy(a); }

    int  get(int i)            { return p[i]; }
    void set(int i, int x)     { p[i] = x; }
};

and an X-returning function, iota, first in standard C++ form,

X iota()                       // return an X with all p[i] = i
{
  X y;                         // create a local X
  for (int i=0; i<Xsize; ++i)  // fill it up
    y.set(i, i); 
  return y;                    // return a copy via X(X&)
}

and then using named return values,

X iota_nrv() return x          // x is the name of the X we are returning
{
  for (int i=0; i<Xsize; ++i)  // fill it up & get out
     x.set(i, i); 
}


First off, the rules associated with named return values are:

    1) The named object is the one received by the caller upon
       function return.

    2) Any kind of constructor, perhaps based on runtime function
       arguments, may be specified for the return value. Thus
       something like X f(int i) return x(i) {...} is legal.

    3) The constructor for the named return value is executed
       before any statements in the function body. 

    4) Only a `return' (or returning by falling off the edge) are
       legal ways to return from a function declaring named return
       values.  Use of `return <expression>' is illegal.

           (Note: (4) is open for negotiation. It is arguable that a
           return <expression> should be legal, meaning to perform
           re-intialization or assignment to the already-initialized
           return value. However, this seems inelegant, error-prone,
           and substantially less simple than just disallowing it.)




The most practical reason for desiring named return values is
efficiency.  Named return values allow one to write a function
returning some large object without also first allocating space for
and creating a local version that is to be somehow built-up solely for
the sake of returning via a copy (i.e., using X(X&)).

In the example, function iota declares local X y, which is allocated,
constructed, modified, and then returned by value (via X(X&)) to
the caller, whereas function iota_nrv directly creates and modifies
the object being sent back to the caller, saving a time-consuming
X(X&) constructor, and also saving the space otherwise needed to
hold two Xs (during X(X&) construction), rather than just one.

Thus, function iota_nrv is about twice as fast as function iota. The
difference can, of course, be made arbitrarily dramatic by increasing
Xsize.

The best counterargument against this efficiency claim is that a
*very* smart compiler could translate function iota into something
like function iota_nrv all by itself. While conceivably true, in
cases of arbitrarily complicated nesting of arbitrarily complicated
functions, eliminating all such useless copying would sometimes
require interprocedural data flow analyses stretching the capabilities
of any optimizing compiler for any language I know. Thus, relying on
automated detection seems unrealistic, especially considering that
this effect can be had in such a simple and natural way with named
return values.



The second reason is in terms of C++ design goals and semantics,
at least as I perceive them. 

[Please forgive the grandiosity of this apparent digression!]

In a language like C++, which supports, to varying extents,
object-oriented, procedural, and functional programming styles, one
can think of objects (and their member variables and methods) as being
central, and procedures and functions as being constructs that use and
extend object capabilities in specific ways.

To illustrate, assume you need procedure setv, a special-purpose
routine that modifies some chunk of an X.

void setv(X& x, int from, int to, int val)
{
  for (int i = from; i <= to; ++i) x.set(i, val);
}

But there is often no reason that setv *must* exist as a stand-alone
procedure. One could obtain the same kind of utility, albeit more
awkwardly, by staying within a pure object-oriented framework and
creating a new subclass that incorporates setv:

class X_with_setv : public X
{
public:

         X_with_setv()      :X()  {}
         X_with_setv(int v) :X(v) {}       
    
    void setv(int from, int to, int val)
         { for (int i = from; i <= to; ++i) set(i, val); }
};

(The constructors are needed to ensure everything inherits transparently.)

In some `pure' object-oriented languages, you might have to do
something like this, but in C++, you can just write setv as a
stand-alone procedure (as before) without all of this fuss, and
with various other advantages.

Enough warm-up. Now consider how one would similarly create a subclass
incorporating function iota if one had to:

class Xiota : public X
{
public:
         Xiota() :X() { for (int i=0; i<Xsize; ++i) set(i,i); }
};

The most defensible way to encapsulate iota is as a constructor, since
it constructs a new object.

The Xiota::Xiota() constructor is suspiciously similar in form to the
iota_nrv function. This is because FUNCTIONS ARE CONSTRUCTORS in C++.

Since C++ allows non-object-based functions, one can think of such
functions as ways of extending constructors, in exactly the same sense
that procedures extend methods (as with setv).  These are both very
useful in cases where providing such extensions does not merit (or
gets into logistic problems resulting from) creation of new
subclasses. 

Even *member* functions that return new objects of the current class
(as is often the case with overloaded operators) fall into this category.
In a sense, a constructive operator like, say, unary negation:

class X // as above
{
  //...
public:
  X      operator - () return x { for (int i=0; i<Xsize; ++i) x.p[i]= -p[i]; }
  //...
};

is just syntactic sugar for either a new constructor,

class X // as above
{
  //...
public:
  enum NEGOP { NEG };
         X(const X& a, NEGOP) { for (int i=0; i<Xsize; ++i) p[i] = -a.p[i]; }
  //
};

or a even new subclass,

class XNeg : public X
{
         XNeg(const X& a) :X() { for (int i=0; i<Xsize; ++i) p[i]= -a.p[i]; }
};


If you accept the argument that functions are extensions and/or
disguised forms of constructors, then it seems only natural that the
use of named return values should nicely parallel existing constructor
syntax/semantics, substituting `return <id><opt-constr>' for
`:<type><constr>'. (Compare rules (1-4) above with similar rules for
constructors).

If it weren't for the probable error-proneness resulting from the
introduction of a different syntactic form for `:', (i.e., following
it by a declaration for functions, but base initializers for
constructors), using the colon instead of the return keyword for named
return values, as in `X f() :x { ... }' might be a more logical
choice. But the `return' syntax seems natural enough, and has some
precedent in other languages.



The only disadvantage I know of is that there is no programmer-defined
control over when the constructor for the named return value is
called. It must be called before any function body statements. But
even this has a compensating advantage: It is impossible to write a
function using named return values that `forgets' to return anything
at all on one or more of its execution paths. (Although, of course, it
is still possible to mistakenly write one that doesn't return what you
had in mind!)



There are still plenty of cases in which the old function syntax is
still useful, often for logistics reasons:

X f(int v) // no way to generate named ret val via a single expression
{
  extern void g(int&);
  g(v);
  return X(v); 
}

This example was also designed to show a construct, `return X(...)'
(rather than a return of some local) that is compiled (probably by
all C++ compilers) to be just as efficient as named return values since
the constructor is in a tail call. This is among the very few cases
where this is true. Its use is especially encouraged for those using
C++ compilers lacking named return values.

Support? Disagreement? Comments? 

Doug Lea, Computer Science Dept., SUNY Oswego, Oswego, NY, 13126 (315)341-2367
email: dl@oswego.edu              or dl%oswego.edu@nisc.nyser.net
UUCP :...cornell!devvax!oswego!dl or ...rutgers!sunybcs!oswego!dl

jima@hplsla.HP.COM (Jim Adcock) (08/09/89)

> 
> X iota()                       // return an X with all p[i] = i
> {
>   X y;                         // create a local X
>   for (int i=0; i<Xsize; ++i)  // fill it up
>     y.set(i, i); 
>   return y;                    // return a copy via X(X&)
> }
> 
> ...
> 
> The best counterargument against this efficiency claim is that a
> *very* smart compiler could translate function iota into something
> like function iota_nrv all by itself. While conceivably true, in
> cases of arbitrarily complicated nesting of arbitrarily complicated
> functions, eliminating all such useless copying would sometimes
> require interprocedural data flow analyses stretching the capabilities
> of any optimizing compiler for any language I know. Thus, relying on
> automated detection seems unrealistic, especially considering that
> this effect can be had in such a simple and natural way with named
> return values.

It doesn't seem to me like it takes a very smart compiler to be able
to figure out that iota uses y as a dummy return variable.  But these
simple cases are precisely where one needs compilers to be a little smarter.
Long and complex functions can afford the cost of the extra copy.  Little
functions can't afford the cost -- but should be easy to optimize.
Let's get C++ compilers that can truly handle the uniquely C++ features
of the language -- initialization, assignment, return values, array 
initialization, registering small structures, efficient this passing, etc.  
These issues are *not* the same as in C compilers -- C++ makes very different 
demands on a compiler's code generating capabilities.

Let's ask C++ compiler writers to design their compilers to meet the state of
the art for the 1990's.  Let's not bollix up the language with a lot more tired
anachronisms like "register."

rjc@maui.cs.ucla.edu (Robert Collins) (08/09/89)

In article <1826@cmx.npac.syr.edu> dl@oswego.edu (Doug Lea) writes:
>
>
>This is an argument for the `official' adoption in C++ of the notion
>of named return values, as have been present in GNU g++ for the last
>few versions.
>
>Named return values are a g++ extension that gives a name to the
>object returned by a function, in a way vaguely similar to named
>return `variables' in Pascal and other languages, but with some
>important C++-specific benefits.
> [ ... ]
>The most practical reason for desiring named return values is
>efficiency.  Named return values allow one to write a function
>returning some large object without also first allocating space for
>and creating a local version that is to be somehow built-up solely for
>the sake of returning via a copy (i.e., using X(X&)).
> [ ... ]
>Support? Disagreement? Comments? 
>

I think named returnn values are a great idea.  I have been using them
since they appeared in g++ (and whined enough to get some of the
bugs fixed :-) ).  I use them primarily for efficiency.  For my
particular application, I have seen a 5-10% reduction in run-time.
This is a big win, since this application has already run for many
hundreds of hours on our Connection Machine.

In addition, I find the syntax easy to write and easy to read, and the
semantics are clear and easy to understand (at least to me).

>Doug Lea, Computer Science Dept., SUNY Oswego, Oswego, NY, 13126 (315)341-2367
>email: dl@oswego.edu              or dl%oswego.edu@nisc.nyser.net
>UUCP :...cornell!devvax!oswego!dl or ...rutgers!sunybcs!oswego!dl

rob collins



















-------------------------------------------------------------------------------
rjc@cs.ucla.edu	                    C++/Paris on the CM2:  The only way to fly.
-------------------------------------------------------------------------------

kearns@read.columbia.edu (Steve Kearns) (08/09/89)

I am in favor of named return values; however....

much of their use can be avoided, I have found.  Many of the libg++
library classes attempt to simulate "value" semantics.  For example,
you might be able to use a matrix class by saying:

matrix m1, m2, m3;
...
m1 = m1 + 2*m2 + m3;

While this is very nice in theory, in practice it can lead to horrible
performance because of the various temporary matrices that are created.  

I think the best near term solution to this problem is to allow the
programmer to explicitly control memory use.  The way this is done is
by declaring destructive plus and times operations.  m1.plus(m2) is a
routine which modifies m1 so that (new)m1 = (old)m1+m2.  Then the
above program could be rewritten:

m1.plus(matrix(m2).times(2)).plus(m3)

(assume that matrix(m2) is a constructor returning a matrix equal to
m2.)  Admittedly, the syntax is much less readable.  On the other
hand, it gives the programmer the chance to optimize his expressions
and significantly reduce the number of large temporaries.  It is also
more "honest":  matrices are NOT good candidates for having value semantics
because their copying time is large.  

By writing in this style when appropriate, most uses of named return values
go away.

-steve
(kearns@cs.columbia.edu)

rjc@maui.cs.ucla.edu (Robert Collins) (08/09/89)

In article <6444@columbia.edu> kearns@cs.columbia.edu writes:
>I am in favor of named return values; however....
> [ ... ]
>matrix m1, m2, m3;
>...
>m1 = m1 + 2*m2 + m3;
> [ rewrite as ]
>m1.plus(matrix(m2).times(2)).plus(m3)

That is REALLY ugly.  I am using C++ so I don't have to write
code like that!

>(assume that matrix(m2) is a constructor returning a matrix equal to
>m2.)  Admittedly, the syntax is much less readable.  On the other
>hand, it gives the programmer the chance to optimize his expressions
>and significantly reduce the number of large temporaries.  It is also
>more "honest":  matrices are NOT good candidates for having value semantics
>because their copying time is large.  

Part of this cost is unnecessary copies that named return values can
help allieviate.

>By writing in this style when appropriate, most uses of named return values
>go away.

Yes, if you write code like that, you don't need named return values.  I
don't know anyone who wants to write code like that!  I think the fact
that people are tempted to write in this sort of style is a very strong
argument for named return values!  Anyone who is considering writing
code in C++ and sees examples in this `efficient' style is going to
run away screaming into the night.  Just say no.

>-steve
>(kearns@cs.columbia.edu)







rob collins
-------------------------------------------------------------------------------
rjc@cs.ucla.edu	                    C++/Paris on the CM2:  The only way to fly.
-------------------------------------------------------------------------------

lgy@blake.acs.washington.edu (Laurence Yaffe) (08/09/89)

In article <6444@columbia.edu> kearns@cs.columbia.edu writes:

>While this is very nice in theory, in practice it can lead to horrible
>performance because of the various temporary matrices that are created.  

	[ various comments about the desirability of explicitly
	  controlling memory use for matrix operations deleted ]

> It is also
>more "honest":  matrices are NOT good candidates for having value semantics
>because their copying time is large.  

>-steve
>(kearns@cs.columbia.edu)

    The claim that frequent copying of matrices causes unacceptable
performance degradation appears to be common dogma, but what real evidence
supports this?  Since most common operations on matrices  (multiplication,
diagonalization, decomposition, inversion, ...) involve order N^3 operations
for N-dimensional matrices, while copying is only order N^2, the overhead
of copying will be significant only if (a) matrices are small and copies
are very frequent (compared to other operations), (b) matrices are so large
that memory limitation intervene, or (c) no O(N^3) operations are being
performed.

    In years of my own work, I've never seen real examples of case (c),
and only a few examples of case (a).  Over quite a range of applications,
I've found that the breakeven point where O(N^2) copies become important
is well under N=10, typically 3 or 4.  And for compute intensive
applications with matrices that small, special methods tend to be more
appropriate (fixed dimension types, inline coding, ...).  I have run
into examples in case (b), most recently in a calculation involving
1280 x 1280 dimensional matrices which needed more than 80 Mb of swap space!
But this type of problem seems to be largely a thing of the past - unless
you have a very fast machine or the patience to do O(N^3) operations on
1000 x 1000 matrices.

    On all the machines I've used, sequentially accessing all matrix elements
in a row is significantly faster than accessing a column (better locality
of reference, faster pointer increment).  And yet surprisingly few canned
matrix multiply routines pre-transpose one of the matrices (or use equivalent
tricks involving an O(N^2) movement of data) in order to take advantage of this
fact.  Absolutely criminal...

    Anyone have real data (or just more anecdotal tales) on the significance
of matrix copies in real applications?

-- 
Laurence G. Yaffe		Internet: lgy@newton.phys.washington.edu
University of Washington	  Bitnet: yaffe@uwaphast.bitnet

lisch@mentor.com (Ray Lischner) (08/10/89)

In <6444@columbia.edu>, kearns@read.columbia.edu writes that

> m1.plus(matrix(m2).times(2)).plus(m3)

can prevent the "horrible performance" of

> m1 = m1 + 2*m2 + m3;

When m1, m2, and m3 are matrices.

If efficiency is that important, then why not define assignment
operators: +=, *=, etc., to do what you call plus(), times(), etc.:

    tmp = m2;
    tmp *= 2;
    tmp += m3;
    m1 += tmp;

I would still rather write readable code, but if, for a given compiler,
the readable version does perform adequately, then I would rather see
assignment operators than plus(), etc.
-- 
Internet: lisch@mntgfx.mentor.com     UUCP: tektronix!sequent!mntgfx!lisch

dl@cmx.npac.syr.edu (Doug Lea) (08/10/89)

I have a great deal of sympathy with Steve Kearns views, but disagree
with his conclusions. For many, `algebraic' classes, including vectors
and matrices, there is a natural object-oriented operations &
semantics, that is substantially different-looking than the more
familiar value-based operations & semantics. I think many such classes
ought to support BOTH.

To compose an argument about why this should be so, I'll scale down,
and consider a class-based re-implementation of simple integers:

class Int
{
  int  rep;
public:
       Int(const Int& a)         :rep(a.rep) {}
       Int(int i = 0)            :rep(i) {}

  void operator = (const Int& a) { rep = a.rep; }

  void negate()                  { rep = -rep; }
  void operator +=(const Int& a) { rep += a.rep; }
  void operator -=(const Int& a) { rep -= a.rep; }
  void operator *=(const Int& a) { rep *= a.rep; }
  void operator /=(const Int& a) { rep /= a.rep; }
};


This definition is very much an object-oriented one. However, I bet
that few programmers would like to use it. Instead of coding integer
operations in a familiar expression-based (value-based) notation like,
for Int a, b, c, d;

   a = (b - a) * -(d / c);     (*)

they would be forced into something akin to assembly language
calculations, hand-translating their intentions (i.e., (*)) into


  Int t1(b);  t1 -= a;         (**)
  Int t2(d);  t2 /= c;
  Int t3(t2); t3.negate();
  Int t4(t1); t4 *= t3;
  a = t4;

Unless, of course, they are pretty good hand-optimizers! In which
case, using some basic, well-known arithmetic optimization (rewriting)
principles, they could write this more efficiently as,

  a -= b; a *= d; a /= c;      (***)

Hardly anyone likes to hand-optimize such expressions. (The fact that
the object operations employ operator notation (`t4 *= t3') instead of
prefix notation (e.g., `t4.mul(t3)') scarcely makes this any more fun.)

Now, it is not hard to completely automate the first (*) => (**)
translation step via

Int operator - (const Int& a)               return r(a) { r.negate(); }
Int operator + (const Int& a, const Int& b) return r(a) { r += b; }
Int operator - (const Int& a, const Int& b) return r(a) { r -= b; }
Int operator * (const Int& a, const Int& b) return r(a) { r *= b; }
Int operator / (const Int& a, const Int& b) return r(a) { r /= b; }

As I mentioned in my previous note, these are perhaps best thought of
as extensions or disguised forms of Int constructors.  Named return
values make these simpler, faster, and more obvious, I think.


My points so far are:

1) For many classes, expression-/value- based operations support a more
    natural programming style, that C++, as a hybrid language, is fully
    capable of supporting.

2) Object-based operations are surely the more central and basic in
    any object-oriented language, since value-based operations may
    be layered on top of the object-based ones.

3) As proponents of functional programming like to argue, the use of
    value-based operations results in code that is almost always
    easier to informally and formally verify for correctness. However,
    layering-in value operations as a `translation' step, makes design
    and verification at the object level easier too, since one need
    `merely' determine that the translation preserves correctness.



The kinds of optimizations seen going from (**) to (***) are further
refinements of this expression translation process, familiar to
assembly language programmers, compiler writers, and those studying
general program transformation techniques (e.g., the work inspired by
Burstall & Darlington).

Automation of such optimizations requires capabilities that seem
currently unavailable in C++. There are a lot of well known `tricks'
(reference counting, temp classes, etc.) that can exploit some of the
most glaring opportunities, but there are no built-in
`compiler-compiler' constructs that would allow programmers to specify
all of the kinds of optimizations possible for these kinds of classes.

Among the most frustrating aspects of all this is that these
optimizations are, on the one hand, fairly obvious and well-known,
but, on the other hand, very tedious and error-prone to carry out
manually every time you write out an expression.  Yet more frustrating
is the fact that nearly any compiler already knows how to optimize
expressions involving built-in data types like int, float, char, but
knows nothing at all about expressions involving user-defined types
like Int.


These seem to be the available options for implementors of such classes:

    1) Refuse to support expression-oriented operations.

    2) Support both the object-oriented methods, and simple
       value-to-object translations, thereby allowing programmers to
       hand-optimize using the object operations if they need to.

    3) Support as much optimization as you can manage from within
       the confines of C++.

    4) Write expression node classes that form expression trees during
       *runtime*, and fully optimize the trees, again during runtime
       before processing. (E.g., ExprNode operator + (Int&, Int&); makes
       a node, and Int::operator=(ExprNode&); evaluates it.)
 
    5) Write class-specific preprocessors, that perform optimized
       translations using knowledge that can't be expressed in C++.

    6) Extend the language.

Choosing (1) means that people will find the class difficult and/or
annoying to use.  Even the purest object-oriented languages provide at
least some arithmetic operator support, probably because programmers
would refuse to use them otherwise. As Michael Tiemann says, code
isn't reusable if it's not usable!

(2) is perhaps the most practical way to proceed right now, and is
fully within the spirit of the C/C++ maxim of `make it right, *then*
make it fast'. (Note, for example, that C/C++, unlike many languages,
already supports many object-based operations on builtin types, like
operator +=, that were designed, in part, to make hand-optimization by
programmers easier.)

I have written a bunch of libg++ classes along the lines of (3).
Unfortunately, such coding often involves cleverness or trickery that
obfuscates otherwise simple designs, and does not have a great payoff
since only a very small subset of common optimizations can be done
within such limitations.

In effect, (4) turns such classes into little interpretors. This *is*
doable, but approaches viability only when the objects and expressions
are so large and complex that the time taken to create and evaluate
the expression tree during runtime is always paid off by the resulting
savings. Moreover, it seems just plain wrong to do compile-time
processing during run time.

I am currently investigating some forms of (5), in part, to scope out
the kinds of issues and problems inherent in (6) about which I
do not yet have any good concrete suggestions.



Among the things I find most interesting about this topic is that
there are some classes (numbers, strings...) in which value-based
programming is seen by most everyone as the most natural and desirable
methodology, others (queues, windows, ...) in which object-oriented
programming is most natural, and some (sets, matrices, ...)  where people
like to use both. I'm not exactly sure why this is so. But a great
attraction of C++ is that there is the possibility that both views can
be accomodated.


Doug Lea, Computer Science Dept., SUNY Oswego, Oswego, NY, 13126 (315)341-2367
email: dl@oswego.edu              or dl%oswego.edu@nisc.nyser.net
UUCP :...cornell!devvax!oswego!dl or ...rutgers!sunybcs!oswego!dl

nagle@well.UUCP (John Nagle) (08/14/89)

In article <1989Aug9.175337.12165@mentor.com> lisch@mentor.com (Ray Lischner) writes:
>If efficiency is that important, then why not define assignment
>operators: +=, *=, etc., to do what you call plus(), times(), etc.:
>
>    tmp = m2;
>    tmp *= 2;
>    tmp += m3;
>    m1 += tmp;
>
     Very nice.  A useful approach to matrix math.

     An interesting thought is that a compiler could optimize 

	m1 += 2*m2 + m3;

     into the above sequence by itself, thus minimizing the intermediate
storage consumed.  This is a strange, but valid, way to optimize arithmetic
expressions generally.  Should a compiler do such a thing?  Should there
be pragmas associated with overloaded operators to advise the compiler on
which operations cost more than others, so that the optimizer can perform
better.   Copying big objects is expensive, and the ability to reduce
it is a worthwhile optimization.  It's worth thinking about what this
means in string operations, where such operations as sequential concatenation
usually result in either ugly source code or multiple recopying.

					John Nagle

SRWMRBD@windy.dsir.govt.nz (ROBERT) (08/16/89)

This is to support Doug Lea's proposal for "named return values". I
don't want to comment on the specific syntax he proposed - simply to say
we need a better way of getting objects back from functions.

I think this is more important for small objects than big ones where one
can use tricks to avoid the copying. For example I presume (I haven't
tested it) in a complex add that a significant amount of time (and
space) is used in the return.

For big objects like big matrices only the pointers and dimensional
information will need be copied at the end of a function or operation,
for functions or operations written by the library developer.

For example if you use my vector/matrix package (see my note of sometime
ago)

Matrix m1(100,100), m2(100,100), m3(100,100);

...

m1 = m1 + m2*2.0 + m3;

will create only one temporary which gets destroyed almost immediately
and does no copying of matrices. (At least it doesn't in Zortech; Doug
Lea found that AT&T C++ could generate extra initialises under some
circumstances).

However if you, a user, write a function  Matrix Sqrt(Matrix&)  then

m1 = Sqrt(m2);

will do one unnecessary copy. That is unless you know how to tell the
initialisation routines that the space occupied by the matrix you are
returning can be recycled.

So I want namded return values or the equivalent to make handling small
objects more efficient and make life simpler for people handling big
objects.

kearns@read.columbia.edu (Steve Kearns) (08/17/89)

Here is another argument for named return values.  

In the olden days, C routines could only return something pointer sized or
smaller.  (Lets ignore floats!)  Later, returning structures was allowed.

This is where a basic conceptual mistake was made.  C is not a "value"
language.  Data structures not only have values, they have locations.
When routines return small structures we can ignore their location 
because they can be copied from place to place cheaply.  For large
objects this is no longer true.  

EVERY OBJECT RETURNED IS RETURNED TO SOME LOCATION.  Therefore the default
semantics should allow the programmer access to this location, and the
special case should be the small structures for which copying is cheap.
Named return values provide this functionality.

-steve
(kearns@cs.columbia.edu)

jss@jra.ardent.com (Jerry Schwarz (Compiler)) (08/18/89)

There are two distinct questions about named function return.

    A) Are they a good idea stylistically?
    B) Do they add something to the language? (I.e. is there something
       you can say with them that you can't without them?)

A is a non-technical question and I will grant that there
are arguments on both sides. My conclusion is that
they are a bad idea, but I will not discuss this issue further
in this note.

The answer to B hinges on some subtle points of C++ semantics.

To answer B we must consider whether there is any difference in 
meaning between the proposed

    X n() return x	// named return value
	... x = ... 		    
	}

and

    X u() {		// unnamed return value
	X x ;
	...
	return x ;
	} 

In C++, functions that return an X always do so by construction.
The above differ in what I call their cannonical interpretation.
According to this interpretation, the x is constructed 
on entry to n in the place where the value is to be returned.  
u constructs x on the stack on entry and uses 
X(X&) to construct the return value just before returning.  

The key question is a subtle one, is it a legal optimization for
u to behave like n.  I believe the answer is yes, and that
having named return values adds nothing to the language.
(Although it forces the compiler to implement the optimization when
naemd return values are used.)

Doug Lea and I have had discussions about this kind of issue
before. He has generally taken the position that such optimizations
should not be allowed. 
That may be  why he wants named return values.

Jerry Schwarz
jss@ardent.com

cline@sun.soe.clarkson.edu (Marshall Cline) (08/18/89)

In article <13118@well.UUCP> nagle@well.UUCP (John Nagle) writes
about optimizing:	m1 += 2*m2 + m3;
into:			tmp = m2;  tmp *= 2;  tmp += m3;  m1 += tmp;

He says:
>Very nice.  A useful approach to matrix math.
>An interesting thought is that a compiler could optimize 
>	m1 += 2*m2 + m3;
>into the above sequence by itself, thus minimizing the intermediate
>storage consumed.  This is a strange, but valid, way to optimize arithmetic
                                           ^^^^^--but see below
>expressions generally.  Should a compiler do such a thing?
> ...
>John Nagle

Unfortunately it is valid _only_ if:		a += b
always does the same thing as:			a = a + b
(and similarly for a *= b and a = a * b, etc).

The meanings of +=, *=, -=, etc, are firmly entrenched in the minds of
C programmers, however C++ doesn't _require_ the same semantics to be
preserved for user-defined types.  For example, there's no reason why
(a *= b) can't open the file named (b) and print the value of (a) to it
[Kids: don't try this at home].  Even "=" doesn't need to assign (although
assignment is its default behavior).

Marshall Cline
--
	__________________________________________________________________
	Marshall P. Cline	Internet: cline@sun.soe.clarkson.edu
	ECE Department		Usenet:   uunet!sun.soe.clarkson.edu!cline
	Clarkson University	Bitnet:   BH0W@CLUTX
	Potsdam, NY  13676	AT&T:     315-268-6591

dl@cmx.npac.syr.edu (Doug Lea) (08/20/89)

Jerry Schwarz contemplates whether an optimizing C++ compiler should be
allowed to optimize a function like u(),

    class X { ... X(X&) { ... } ... }; 

    X u() {		// unnamed return value
	X y ;
	...
	return y ;
	} 

into that which would be produced via a function like n()

    X n() return x	{ // named return value
	... x ... 		    
	}

The question is whether a compiler may *legally* skip the X(X&)
constructor to get the value of y out of function u by constructing
and dealing with y as if y itself were the return value, thus, in this
case at least, automating some of the efficiency benefits of
named return values. 

Jerry and I have indeed been through a few exchanges on such points.
To summarize some of the discussions:

The C++ reference manual does not enumerate exactly those conditions
under which X(X&) will or will not be invoked, and further, does not
impose any restrictions upon user definitions of X(X&).  Therefore, as
I demonstrated in a posting last spring, one can write an X(X&)
constructor that possesses arbitrary side effects, and the results of
the corresponding program will differ depending on whether you use a
compiler that does invoke X(X&) in situations like getting the return
value out of function u() versus one that does not.  This has some
pragmatic interest since various C++ compilers `elide' out X(X&)
constructors in some circumstances but not others.

The use of named return values happily and successfully evades this
issue, at least with respect to return values, so I didn't bother to
get into it in my previous NRV postings.  But some kind of resolution
of this part of the story could be had by tightening up the
description of X(X&) semantics just enough to better reflect the
general assumptions that are or could be made inside of cfront, g++,
and probably all other C++ compilers. Jerry and I once came up with
something along the lines of

    Given any class X, and any X object `obj', when another object
    with the value of obj is required, C++ compilers may optionally
    use obj instead of a new object, `newobj', constructed via X(obj)
    if obj is not subsequently modified differently than newobj during
    newobj's potential lifetime.  Similarly, compilers may in such
    cases use any other X object constructed via X(obj), but never
    since or subsequently modified differently than obj during obj's
    lifetime.  X(X&) constructors should be defined in such a way that
    these optional compiler-generated actions do not change the
    meanings of programs.

To be more complete, this description would have to be accompanied by
an emumeration of those cases where any such value/object is
`required'.  The current reference manual appears to list these,
although not all in one place, or in these terms. 

The idea is to spell out those situations in which a compiler may safely
(1) alias and/or reuse objects (as in the case of a local and a
return value) rather than forcing copies via X(X&), and 
(2) invoke X(X&) (say, into a register) even in cases where it is not
explicitly needed if it somehow discovered that this would improve 
overall efficiency.  

The reference manual currently touches on some of these issues in
terms of `generating temporaries', leading one to believe that it is
describing case (2) here, in situations where it is really referring
to unexploited instances of case (1), i.e., the fact that compilers
don't have to guarantee that they will optimize out logically
required, but obviously unnecessary X(X&)'s.

Part of the reason that all this is controversial is that, both from a
safety perspective and in order to enable more aggressive optimization
(safety, correctness, and optimizability are almost always just
different ways of looking at the same problem), it would be nice to
banish *all* side effects from X(X&) constructors. However, it does
not seem at all desirable to disallow `innocuous' side effects, such as
reference counting manipulations.  Additionally, a case can be made
that programmers should be allowed to write non-innocuous-side-effect
laden X(X&)'s and to have a way to disable optimizations in order to
force X(X&) constructors whenever they are logically or explicitly
required, in a way analogous to how ANSI C `volatile' variables are
handled, although I am no longer convinced that this is especially
worthwhile.

The important practical implication is that programmers should write
X(X&) constructors that build faithful copies of their arguments
without any kinds of side effects that would cause programs to behave
differently depending on whether the constructors were actually called
or not when they are logically required.  Of course, it is impossible
for a compiler alone to prove to itself whether X(X&) side effects are
considered by the programmer to be innocuous or not, or even whether a
compiler-generated or programmer-defined X(X&) really does make a
`correct' copy, so any kind of restriction on X(X&) is mostly
unenforceable by compilers.

Again, these semantic issues do not impact one way or another my other
arguments for the utility of named return values. In particular, they
do not alter the fact that named return values provide a deterministic
guarantee that X(X&) will NOT be invoked, whereas, even with further
refinement of X(X&) semantics, the question of whether any compiler
actually can and does optimize out all return-based X(X&) constructors
remains a non-trivial compiler implementation matter.

Why is return-value X(X&) optimization hard for a compiler to guarantee?
Consider a function with lots of local X's, and lots of conditionals,
loops, inline and non-inline function calls, returns, etc. It would
take analyses at the edge of current state of the art compiler
technology to discover which variable(s) could safely be aliased to
the return value slot, and how to manage the slot. I am all in favor
of compiler implementors adding such features to C++ compilers.  But
even with these kinds of analyses, it seems naive to believe that a
compiler could always arrive at code that would always be as good as
that specified in a simple and natural manner by a programmer using
named return values. Permitting such control over how ones code gets
compiled is surely within the spirit of C and C++.


Doug Lea, Computer Science Dept., SUNY Oswego, Oswego, NY, 13126 (315)341-2367
email: dl@oswego.edu              or dl%oswego.edu@nisc.nyser.net
UUCP :...cornell!devvax!oswego!dl or ...rutgers!sunybcs!oswego!dl