Document Number: N2376=07-0236
2007-07-30
Alisdair Meredith <alisdair.meredith@uk.renaultf1.com>
Michael Wong <michaelw@ca.ibm.com>
Jens Maurer <Jens.Maurer@gmx.net>

Inheriting Constructors (revision 2)

This paper is a revision of N2254 "Inheriting Constructors (revision 1)" by Alisdair Meredith, Michael Wong, Jens Maurer.

Problem Description

There is often a desire to initialize a derived class with exactly the same set of constructors as its base. This typically ends up with a series of tediously simply forwarding declarations and definitions. This work could be more easily handled by the compiler, is less error-prone when handled by the compiler, and the intent is clearer to read if the forwarding problem can be reduced to a single statement.

There are also a couple of cases where the forwarding constructors cannot be declared by the user, such as for a dependant base class where a class template derives from one (or more) of its own template type parameters.

Outline of the Solution

Initial analysis was supplied by Francis Glassborow (N1583) and after further feedback from the evolution group, the direction was to word the simplest feature possible without making any effort to support extended use cases. The more complicated examples will probably be handled by a different language feature - a variadic constructor template.

The basic idea is to implicitly declare a set of forwarding constructors with a syntax very similar to a using declaration. Like other implicitly declared constructors, they are only implicitly defined if they are used.

Copy and default constructors are not forwarded, deferring to the existing rules for implicitly declaring copy/default constructors.

As forwarding constructors are implicitly declared, they do not inhibit the implicit declaration of the default constructor.

Forwarding constructors retain the throw spec, explicitness, constexpr qualifier and = delete flag of the base constructor.

Forwarding constructors are defined in a similar manner to an implicitly defined default constructor, so additional bases and data members are subject to default initialization. Arguments to the base class constructor are 'perfectly forwarded' using rvalue-references.

User declared constructors inhibit forwarding for that particular signature, much as user declaration of a function would hide a specific signature when using declarations are used with regular functions.

If multiple fwd-constructors-declarations declare the same signature, the program is ill-formed, even if those constructors are not used. This can be worked around with a user-declared constructor instead, which inhibits the problem declarations.

It is often an error for multiple fwd-constructors-declarations to refer to the same base class. However, if a fwd-constructor-declaration does not actually lead to any implicit declarations, then there will be no problems if it is ued multiple times, although this is storing up fragile base class problems if constructors are added to that base in the future.

A program is ill-formed if a fowarding constructor is used that forwards to a base class constructor that is declared private. Note that this is different to the using declarations with regular functions, that are ill-formed even if the private function is not used. It is consistent with the way a variadic constructor template would work though.

Typically, forwarding constructor definitions for classes with virtual bases will be ill-formed, unless the virtual base supports default initialization, or the virtual base is a direct base, and named as the base forwarded-to. Likewise, all data members and other direct bases must support default initialization, or any attempt to use a forwarding constructor will be ill-formed. Note: ill-formed when used, not declared.

Summary of Changes

Changes in the wording from the previous paper: Changes accumulated since CWG review in Oxford:

Proposed Changes to the Working Paper

Change the grammar before 9.2p1 as indicated:

member-declaration:
     decl-specifier-seqopt member-declarator-listopt ;
     function-definition ;opt
     ::opt nested-name-specifier templateopt
     unqualified-id ;
     using-declaration
     static_assert-declaration
     template-declaration
     fwd-constructors-declaration

As fwd-constructors-declarations can declare an empty set of constructors, amend 9.2p1 as indicated. Note we also allow for static assertions, which appear to be disallowed with the current wording.

Except when used to declare friends (11.4) or, to introduce the name of a member of a base class into a derived class (7.3.3,11.3), or as a static_assert-declaration (7 dcl.dcl), member-declarations declare members of the class, and each. Each such member-declaration other than a fwd-construcutor-declaration shall declare at least one member name of the class. A member shall not be declared twice in the member-specification, except that a nested class or member class template can be declared and then later defined.

Create a new section 12.9 class.fwd [not shown in markup here]:
12.9 Forwarding Constructors [class.fwd]
fwd-constructors-declaration:
     using default ::opt nested-name-specifieropt class-name

A fwd-constructors-declaration declares a set of forwarding constructors. The class named shall be a direct base class of the class being defined.

For each non-template constructor other than a default or copy constructor in the class named in the fwd-constructors-declaration , a constructor is implicitly declared with the same parameter-type-list (8.3.5 dcl.fct), exception-specification (15.4 except.spec), absence or presence of explicit (7.1.2 dcl.fct.spec), and absence or presence of constexpr (7.1.6 dcl.constexpr), unless there is a user-declared constructor with the same signature in the class where the fwd-constructors-declaration appears. Similarly, for each constructor member function template in the class named in the fwd-constructors-declaration, a constructor member function template is implicitly declared with the same template parameter list (14.1 temp.param), parameter-type-list (8.3.5 dcl.fct), exception-specification (15.4 except.spec), absence or presence of explicit (7.1.2 dcl.fct.spec), and absence or presence of constexpr (7.1.6 dcl.constexpr) unless there is an equivalent user-declared constructor member function template (14.5.5.1 temp.over.link) in the class where the fwd-constructors-declaration appears. [Note: Default and copy constructors may be implicitly declared as specified in 12.1 class.ctor and 12.8 class.copy.]

A constructor so declared has the accessibility as if it were declared with the access-specifier that applied to the corresponding constructor in the base class. It is deleted if the corresponding base class constructor is deleted (8.4 dcl.fct.def).

An implicitly-declared forwarding constructor for a class is implicitly defined when it is used (3.2 basic.def.odr) to create an object of its class type (1.8 intro.object). An implicitly-defined forwarding constructor performs the set of initializations of the class that would be performed by a user-written inline constructor for that class with a mem-initializer-list whose only mem-initializer has a mem-initializer-id that names the base class named in the fwd-constructors-declaration and an expression-list as specified below, and with an empty function body (12.6.2 class.base.init). If that user-written constructor would be ill-formed, the program is ill-formed. Each expression in the expression-list is of the form static_cast< T&&>(p) where p is the name of the corresponding constructor parameter and T is the declared type of p.

[ Example:


struct B1 {
  B1( int ) {}
};

struct B2 {
  B2( double ) {}
};

struct D1 : B1 {
  using default B1; // implicitly declares D1( int )
  int x;
};

D1 d(6);        // ok; d1.x is not initialized
D1 e;           // error: base class B1 has no default constructor

struct D2 : B2 {
  using default B2; // ok, implicitly declares D2( double )
  B1 b;
};

D2 f(1.0);      // error: use of D2( double ) is ill-formed

template< class T >
struct D : T {
  using default T;   // declares all constructors from class T
  ~D() { clog << "Destroying wrapper" << endl; }
};

Class template D wraps any class and forwards all its constructors, while writing a message to the standard log whenever an object of class D is destroyed.

]

[Note: If two fwd-constructors-declaration declare forwarding constructors with the same signatures, the program is ill-formed (9.2 class.mem).

[ Example:


struct B1 {
  B1( int );
};

struct B2 {
  B2( int );
};

struct D1 : B1, B2 {
  using default B1;
  using default B2;  // ill-formed implicitly declaring same ctor twice
};

struct D2 : B1, B2 {
  using default B1;
  using default B2;
  D2( int );  // user declaration prevents conflict
};

] ]

 

While drafting the wording for this paper, it occurred to one of the authors that the standard currently defines "default constructor" in terms of whether a constructor can be called with no arguments. Thus, for a class C, C(int x = 42) is a default constructor. An implicitly declared default constructor could have that signature, which seems a bit surprising. Unrelated to the proposed semantics in this paper, we propose to change 12.1 class.ctor paragraph 5 as follows
A default constructor for a class X is a constructor of class X that can be called without an argument. If there is no user-declared constructor for class X, a default constructor with no arguments is implicitly declared. ...

Acknowledgements

Bjarne Stroustrup gave repeated encouragement for the authors to persevere and return with the simplest proposal possible. Steve Adamczyk proposed the using default T; syntax.