Document Number: N2438=07-0308
2007-10-04
Alisdair Meredith <alisdair.meredith@uk.renaultf1.com>
Michael Wong <michaelw@ca.ibm.com>
Jens Maurer <Jens.Maurer@gmx.net>

Inheriting Constructors (revision 3)

This paper is a revision of N2376 "Inheriting Constructors (revision 2)" 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 inheriting 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 inheriting 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 inheriting constructors are implicitly declared, they do not inhibit the implicit declaration of the default constructor.

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

inheriting 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 inheriting-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 inheriting-constructors-declarations to refer to the same base class. However, if a inheriting-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 an inheriting 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, inheriting 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 inheriting constructor will be ill-formed. Note: ill-formed when used, not declared.

Summary of Changes

Changes since n2376: Changes since n2254: Changes accumulated since CWG review in Oxford:

Proposed Changes to the Working Paper

Add a new keyword to 2.11 Table 3: Keywords

base_constructors

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
     inheriting-constructors-declaration

Amend 9.2p1 as indicated:

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 an inheriting-constructor-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.inherit [not shown in markup here]:
12.9 Inheriting Constructors [class.inherit]
inheriting-constructors-declaration:
     using base_constructors ::opt nested-name-specifieropt class-name ;

An inheriting-constructors-declaration declares a set of inheriting 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 inheriting-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 (12.3.1 class.conv.ctor), and absence or presence of constexpr (7.1.5 dcl.constexpr), unless there is a user- declared constructor with the same signature in the complete class where the inheriting-constructors-declaration appears. Similarly, for each constructor template in the class named in the inheriting-constructors-declaration, a constructor 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 (12.3.1 class.conv.ctor), and absence or presence of constexpr (7.1.5 dcl.constexpr) unless there is an equivalent user-declared constructor template (14.5.5.1 temp.over.link) in the complete class where the inheriting-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 same access as 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 inheriting 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 inheriting 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 inheriting-constructors-declaration and an expression-list as specified below, and with an empty compound-statement in its 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 base_constructors B1; // implicitly declares D1( int )
  int x;
};

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

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

D2 f(1.0);      // error: B1 has no default constructor

template< class T >
struct D : T {
  using base_constructors T;   // declares all constructors from class T
  ~D() { std::clog << "Destroying wrapper" << std::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.

]

If two inheriting-constructors-declarations in a class declare constructors with the same signature, the program is ill-formed (9.2 class.mem, 13.1 over.load). [Note: Since the inheriting constructors declared by the first inheriting-constructors-declaration are not user-declared constructors, the second inheriting-constructors-declaration may attempt to redeclare a constructor.]

[ Example:


struct B1 {
  B1( int );
};

struct B2 {
  B2( int );
};

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

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

]

 

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 having no parameters 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.