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.