ISO/ IEC JTC1/SC22/WG21 N1395

Document Number: N1395 = 02-0053
Programming Language C++
November 8, 2002



Aspects of Forwarding (was C++ Support For Delegation)
Lois Goldthwaite
Lois@LoisGoldthwaite.com


This paper is a follow-up to N1363 in the post-Curacao mailing. A small
subcommittee of the Evolution Group (consisting of Lois Goldthwaite, Francis
Glassborow, and Daveed Vandevoorde) discussed that paper and came to the
conclusion that delegation is but one facet of the broader issue of forwarding
functions.  Accordingly, it was deemed worthwhile to attempt to summarize all
(or at least most) of the problems in one place, to provide a basis for
further discussion of the subject.  The following paragraphs attempt to bring
together various ideas that at various times have been formally proposed,
mooted in the bar, or muttered behind the bicycle shed. If this summary has
overly simplified the proposals, the author apologizes. No conclusions or
recommendations are offered.


A. Delegation (see N1363) -- Novices often attempt to use C++ inheritance
mechanisms to express design constructs where containment and delegation
would provide a superior solution. If C++ provided syntax directly supporting
delegation to a sub-object, perhaps there would be less (mis)use of
inheritance. On the other hand, C++ has a perfectly good mechanism within the
existing language -- forwarding functions -- to express delegation, so it is
hard to argue for a change to the core language for this purpose alone. To
support the pimpl (Cheshire Cat) idiom, the original paper covers delegating
to a member object which is a real pointer, but (controversially) not to a
smart pointer.

B. Forwarding an arbitrary list of arguments to another function (see N1385)
-- When creating a generic wrapper for a class or function type, it can be
difficult to handle combinations of arguments needed to construct the class
object or to invoke the wrapped function. One reason for creating such a
generic wrapper is to provide a proxy for the class type; a reason for the
second usage is to provide a functor which binds certain pre-set values to
some arguments and then invokes another function.

Maintaining const correctness for arguments can provoke a combinatorial
explosion of overloads. Additional problems are that non-const references
cannot bind to rvalues, and literal values sometimes produce surprising
results.  The paper proposes either a small core change in the way argument
types are deduced, or a new syntax (shared with the move semantics proposal,
N1377) to enable binding a non-const ref to an rvalue.

C. Polymorphic function object wrappers (see N1375) -- This proposal would add
two new class templates to the standard library: function<> and
reference_wrapper<>, along with some supporting code. The proposed function
object wrapper is designed to mimic function pointers in both syntax and
semantics, but generalize the notion to allow the target of a function object
wrapper to be any function object, function pointer, or member function
pointer that can be called given the return and argument types supplied to the
function object wrapper.  This generic-function-pointer-thingy would make it
easier to construct OO-style callback functions, as well as to assemble
functors from collections of third-party library code. Some relaxation of
the type signature matching rules is suggested, to allow conversions in
arguments and result type. The reference_wrapper template class stores a
reference (or const reference) to an object in a CopyConstructible and
Assignable wrapper, so that references can be manipulated in many of the same
contexts as value types.


D. Binder libraries -- Several binder libraries (N1375 mentions Boost.Lambda,
Boost.Bind, Phoenix, FC++, and FACT!) have the aim of enabling composition of
function objects to create new function objects.  The standard library makes a
step in this direction with bind1st and bind2nd.


E. Generic proxies -- Many uses can be postulated for a generic proxy class
which could intercept and pass on calls to any object. Such a proxy could
provide marshalling/unmarshalling for distributed computing, or begin/end
transaction processing. Creation of generic proxies would be simplified if
some of the above proposals were adopted (and particularly if functions could
be delegated to smart pointers inside wrappers, as mentioned in the
'Delegation' paragraph above.)

F. Automatic inheritance of constructors -- Consider a base class with several
constructors and a class derived from it which needs to inherit those
constructors without adding any additional ones. Currently this can only be
achieved by writing the same number of corresponding constructors for the
derived class:

class Base
{
public:
   Base();
   Base( int x );
   Base( int x, double y );
   Base( int x, double y, std::string name );
};

class Derived : public Base
{
    Derived();
    Derived( int x) : Base( x ) { }
    Derived( int x, double y ) : Base( x, y ) { }
    Derived( int x, double y, std::string name ) : Base( x, y, name ) { }
};

It has been suggested that some mechanism be formulated whereby Derived can
inherit all of Base's constructors without the necessity of manually
duplicating the declarations.


G. Forwarding constructors -- Again, consider a class which has several
constructors, taking various arguments. Some of the constructors are used to
provide sensible default values for any arguments not specified by the
programmer:

class Something
{
   int i;
   double d;
   std::string id;
public:
   Something();
   Something( int x );
   Something( int x, double y );
   Something( int x, double y, std::string name );
   Something( double y );
   Something( double y, std::string name );
   Something( std::string name );
   Something( int x, std::string name );
};

Coding could be simplified, and the possibility of errors reduced, if one
constructor could forward to another one. The syntax is modelled after that
currently used for calling base class constructors in an initializer list:

// this is the only "real" ctor; others forward to it:
Something::Something( int x, double y, std::string name ) : i(x),
                                                            d(y),
                                                            id(name)
{
   // other processing goes here
}

Something::Something( int x ) : Something( x, 0.0, "AnonymousInt" ) { }

Something::Something( double y ) : Something( 0, y, AnonymousDouble" ) { }

Something::Something( std::string name ) : Something( 0, 0.0, name ) { }

and so forth for other combinations of arguments. This example is trivial, but
of course the "real" constructor might perform some complicated bit of
processing which would otherwise have to be repeated by each constructor or
factored into a function which is called by each constructor.

H: Library Migration, especially in respect to namespace std (see N1344) --
When a library is upgraded, most of its functions usually remain unchanged
from old version to new. A few objects and functions may be added, a few
others may be changed, and a few deprecated or even eliminated. Library
authors and users have to cope with problems of using old code with the new
library, changing old code to work with the new library, or even using the old
and new versions of the library from a single program. (A separate issue is
maintaining binary compatibility, if desired.) Maintaining the common code
base is simplified if duplication of source code across the old and new
versions can be prevented. To eliminate duplication, some sort of "strong
typedef" or "strong using declaration" may need to be added to the core
language. With this "strong declaration," a reference to a declaration in the
new namespace could transparently forward to code in the old namespace.

I. Strong typedefs -- For purposes of better type safety, rather than library
migration, some people would like the ability to create a copy of an existing
type, rather than just an alias for it. This would include the abiity to
create copies of built-in types as well as user-defined types:

        strong_typedef int day;
        strong_typedef int month;
        strong_typedef int year;

        struct date
        {
            year y;
            month m;
            day d;
            long calculate_my_julian_day();
         };

         strong_typedef date holiday;

Such types would be distinguishable from the original for overloading or
derivation. Most people, though not all, believe that implicit conversions to
the original type should be prohibited (explicit conversions are allowable).
Strong typedefs could be thought of as a kind of forwarding, so that the code
from the original class can be reused.




A common element in all of the above proposals is a desire to reduce the
amount of boilerplate code that must be produced, often manually.  Writing
less code is a Good Thing. The resulting program ideally becomes more robust
and maintainable. Also, these proposals provide an additional level of
abstraction with which to express the intent of design constructs.