Topic: Named Return Value proposal


Author: "E. Robert Tisdale" <edwin@netwood.net>
Date: 1999/03/27
Raw View
I propose a new feature for the C++ programming language
which permits the programmer to name and initialize
a function return value then elide the copy constructor
upon return from the function.

The following test program illustrates the problem:

----------------------------------------------------------------------
// test.cc

#include<iostream>

class X {                       // a dynamic array class
  // representation
  int*  p;                      // pointer to a huge array
  int   n;                      // length of the huge array
  public:
  // constructors
  explicit
  X(int k):                     // explicit constructor definition
    p(new int[k]), n(k) { }
  X(const X& x);                // copy constructor declaration
 ~X(void) {                     // destructor definition
    if (p) delete [] p; }
  // functions
  int   length(void) const { return n; }
  // operators
  const
  int&  operator [](int j) const { return p[j]; }
  int&  operator [](int j)       { return p[j]; }
  };

X::X(const X& x):               // copy constructor definition
  p(new int[x.n]), n(x.n) {
  cerr << "The copy constructor was called!" << endl;
  for (int j = 0; j < n; j++)   // copy the array
    p[j] = x.p[j];
  }

// user defined functions

#define __return__

inline
X       f0(int n) {
  cerr << "The copy constructor may be called for f0(int)." << endl;
  return X(n);
  }
inline
X       f1(int n) {
  X __return__ x(n);
  for (int j = 0; j < n; j++)   // Modify x.
    x[j] = j;
  cerr << "The copy constructor may be called for f1(int)." << endl;
  return x;
 }
inline
X       g0(X __return__ x) {
  cerr << "The copy constructor may be called for g0(X)." << endl;
  return x;
  }
inline
X       g1(const X& x) {
  cerr << "The copy constructor must be called for g1(const X&)." << endl;
  return x;
  }

int
main () {
  cerr << endl
       << "The copy constructor may be called for X x0 = f0(32);" << endl;
  X x0 = f0(32);
  cerr << endl
       << "The copy constructor may be called for X x1 = f1(32);" << endl;
  X x1 = f1(32);
  cerr << endl
       << "The copy constructor may be called for X y0 = g0(f0(32));" << endl;
  X y0 = g0(f0(32));
  cerr << endl
       << "The copy constructor may be called for X y1 = g1(x1);" << endl;
  X y1 = g1(x1);
  cerr << endl
       << "The copy constructor must be called for X z0 = y0;" << endl;
  X z0 = y0;
  return 0;
  }
----------------------------------------------------------------------

I compiled and ran the `test' program as follows:

----------------------------------------------------------------------
$ uname -rms
Linux 2.0.36 i686
$ g++ --version
egcs-2.90.29 980515 (egcs-1.0.3 release)
$ g++ -O2 -felide-constructors -o test test.cc
../test

The copy constructor may be called for X x0 = f0(32);
The copy constructor may be called for f0(int).

The copy constructor may be called for X x1 = f1(32);
The copy constructor may be called for f1(int).
The copy constructor was called!

The copy constructor may be called for X y0 = g0(f0(32));
The copy constructor may be called for f0(int).
The copy constructor may be called for g0(X).
The copy constructor was called!

The copy constructor may be called for X y1 = g1(x1);
The copy constructor must be called for g1(const X&).
The copy constructor was called!

The copy constructor must be called for X z0 = y0;
The copy constructor was called!
$
----------------------------------------------------------------------

The programmer simply inserts the `return' keyword
between the type and the name in a declaration
to identify the name as a reference to the return value.

----------------------------------------------------------------------
X       f(int n) {
  X return x(n);                // x is a reference to the return value.
                                // Call the X(n) constructor to initialize it.
  for (int j = 0; j < n; j++)   // Modify x.
    x[j] = j;
  return x;                     // Don't call the copy constructor!
                                // Don't call the destructor for x!
 }

X       g(X return x) {         // x is a reference to the return value.
  for (int j = 0; j < n; j++)   // Modify x.
    x[j] = j;
  return x;                     // Don't call the copy constructor!
                                // Don't call the destructor for x!
  }
----------------------------------------------------------------------

The constructor which would normally be called
to initialize a temporary argument for X g(X)
would initialize the return value for X g(X return) instead
even if the the return value is not a temporary:

----------------------------------------------------------------------
int
main () {
  X x(n);
  X y = g(x);                   // Don't create any temporary values.
                                // Call the copy constructor
                                // to initialize y from x directly
                                // then pass a pointer to y
                                // as the return value for g(X).
  return 0;
  }
----------------------------------------------------------------------

The new feature would help single pass C++ compilers identify x
as a reference to the return value and elide the copy constructor.
It would help all C++ compilers optimize the calling program
when function X g(X return) is a library function
and only the function prototype is visible to the C++ compiler.

The portable application programmer can insert the `__return__' macro
instead of the `return' keyword into declarations and prepend

        #define __return__ return

for compilers which do support the proposed feature or

        #define __return__

for compilers which do not support the proposed feature.


E. Robert Tisdale <edwin@netwood.net>
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: sbnaran@fermi.ceg.uiuc.edu (Siemel Naran)
Date: 1999/03/28
Raw View
On 27 Mar 99 20:23:49 GMT, E. Robert Tisdale <edwin@netwood.net> wrote:

>I propose a new feature for the C++ programming language
>which permits the programmer to name and initialize
>a function return value then elide the copy constructor
>upon return from the function.

The standard is even further advanced than this proposal.
The standard allows compilers to automatically detect the
variable to be returned and eliminate a redundant call to
the copy constructor and destructor by constructing this
object in the return value space.  This allowance goes by
the name 'return value optimization'.  Eg,

X f1() { return X(); }
   // unoptimized: create local X, copy local into return space, destroy local
   // optimized  : create X in return space
   // most compilers do the optimization for unnamed locals

X f2() { X x; return x; }
   // unoptimized: create local x, copy local into return space, destroy local
   // optimized  : create x in return space (and don't destroy it)
   // most compilers don't do the optimization for named locals
   // but egcs lets you name return variables manually


This optimization has three implications:

1. An accessible copy constructor is always required, even if
   it is not used.  (In the event that the compiler does the
   return value optimization, the copy constructor is not needed.
   But the compiler isn't required to do the optimization, so in
   general, the copy constructor is needed.)  In most contexts,
   this means that the copy constructor should be public.

2. One should not rely on side effects in the copy constructor
   or destructor of X if one returns X objects anywhere in the
   program.  Consider sentry objects, whose destructor performs
   some routine cleanup task:
      void f() { Sentry s; /*stuff*/; } // 's' destroyed
   Because Sentry::~Sentry() has side effects, we should not
   write functions that return Sentry objects.
      Sentry f() { return Sentry(); } // unpredictable
   This is because the side effects in the destructor may occur
   0 or 1 times, depending on whether the optimization is
   performed or not.  Thus, our program has unpredictable
   results.

3. Similarly, one should ensure that copies are identical, else
   the program will have unpredictable results.


If the copy constructors and destructors of our building block
classes, like vector<> string<> complex<>, meet (2) and (3),
then the compiler generated copy constructors and destructors
for objects composed of these will also meet (2) and (3).  Eg,
   struct X {
      std::vector<int> v;
      std::string s;
      std::complex<double> c;
   };
The compiler generated copy constructor X::X(const X&) has no
side effects because v's and s's and c's copy constructor have
no side effects.  Similar remarks hold for the destructor.

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]