Document Number: n2119(2006-0189)
2006-10-19
Alisdair Meredith <alisdair.meredith@uk.renaultf1.com>
Michael Wong <michaelw@ca.ibm.com>
Jens Maurer <Jens.Maurer@gmx.net>

Inheriting Constructors

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 the class template derives from one (or more) of its own template type parameters.

Outline of the Solution

After 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 extend using declarations to support constructors. This falls out of the grammar today (no grammar changes required) and simply extends the semantic rules to cover the new case.

When a using declaration names a base class constructor, a number of forwarding constructors are implicitly declared in the derived class. Like other implicitly declared constructors, they are only implicitly defined if they are used.

Copy and default constructors and 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 and explicitness of the base constructor.

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.

It is an error for multiple using declarations to refer to the same base class. If multiple using directives 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.

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.

Note that typedef names that alias a direct base will work in the same way, and it is even possible to mix and match names either side of the ::.
For example:


struct derived : base {
  typedef base inherited;
};

The following would all be valid using declarations, and all mean the same thing. Attempting to use any combination of more than of these would be ill-formed for the same reason that multiple declarations of the same name are ill-formed.
  • using base::base;
  • using base::inherited;
  • using inherited::base;
  • using inherited::inherited;

    Proposed Changes to the Working Paper

    Change 7.3.3 namespace.udecl paragraph 1 as follows
    A using-declaration introduces a name into the declarative region in which the using-declaration appears. That name is a synonym for the name of some entity declared elsewhere.

    [grammar snippet is ok]

    The member name specified in a using-declaration is declared in the declarative region in which the using-declaration appears. [Note: only the specified name is so declared; specifying an enumeration name in a using-declaration does not declare its enumerators in the using-declaration's declarative region. -- end note ] Moreover, if a using-declaration names a constructor (3.4.3.1 class.qual), it implicitly declares a set of constructors in the class in which the using-declaration appears (12.9 class.fwd).

    Change 7.3.3 namespace.udecl paragraph 3 as follows
    In a using-declaration used as a member-declaration, the nested-name-specifier shall name a base class of the class being defined. If such a using-declaration names a constructor, it shall name a constructor of a direct base class, otherwise it Such a using-declaration introduces the set of declarations found by member name lookup (10.2, 3.4.3.1).
    Change 7.3.3 namespace.udecl paragraph 4 as follows
    [ Note: since constructors and destructors do not have names, a using-declaration cannot refer to a constructor or a destructor for a base class. Since specializations of member templates for conversion functions are not found by name lookup, they are not considered when a using-declaration specifies a conversion function (14.5.2). -- end note ] ...
    Change 7.3.3 namespace.udecl paragraph 14 as follows
    When a using-declaration brings names from a base class into a derived class scope, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (8.3.5), and cv-qualification in a base class (rather than conflicting). [Note: for using-declarations that name a constructor, see 12.9 class.fwd]
    Change 7.3.3 namespace.udecl paragraph 17 as follows
    All instances of the name mentioned in a using-declaration shall be accessible. In particular, if a derived class uses a using-declaration to access a member of a base class, the member name shall be accessible. If the name is that of an overloaded member function, then all functions named shall be accessible. The base class members mentioned by a using-declaration shall be visible in the scope of at least one of the direct base classes of the class where the using-declaration is specified. If the using-declaration names a constructor, all base class constructors forwarded to (12.9 class.fwd) shall be accessible. ...
    Change 7.3.3 namespace.udecl paragraph 18 as follows
    The alias or the constructors created by the using-declaration has the usual accessibility for a member-declaration.
    Create a new section 12.9 class.fwd [not shown in bold here]:
    12.9 Forwarding Constructors [class.fwd]

    A using-declaration (7.3.3 namespace.udecl) that names a constructor implicitly declares a set of forwarding constructors . Candidates are all constructors of the class named in the using-declaration, except default and copy constructors and those with an ellipsis parameter. For each non-template candidate, a constructor is implicitly declared with the same parameter-type-list (8.3.5 dcl.fct), exception-specification (15.4 except.spec), and absence or presence of explicit (7.1.2 dcl.fct.spec), unless there is a user-declared constructor with the same signature in the class where the using-declaration appears. Similarly, for each constructor member function template among the candidates, 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), and absence or presence of explicit (7.1.2 dcl.fct.spec), unless there is an equivalent user-declared constructor member function template (14.5.5.1 temp.over.link) in the class where the using-declaration appears. [Note: Default and constructors (12.8) may be implicitly declared as specified in 12.1 class.ctor and 12.8 class.copy.]

    [Note: If two using-declarations declare forwarding constructors with the same parameter-type-lists, the program is ill-formed (9.2 class.mem).

    
    struct B1 {
      B1( int, int ) {}
    };
    
    struct B2 {
      B2( int, int ) {}
    };
    
    struct D1 : B1, B2 {
      using B1::B1;
      using B2::B2;  // ill-formed implicitly declaring same ctor twice
    };
    
    struct D2 : B1, B2 {
      using B1::B1;
      using B2::B2;
      D2( int, int ) {} // User declaration resolved conflict
    };
    
    
    ]

    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 using-declaration and an expression-list that forwards all constructor arguments in order (12.6.2 class.base.init), and with an empty function body. If that user-written constructor would be ill-formed, the program is ill-formed. The base class constructor referred to in the mem-initializer is called the constructor forwarded to.

    Examples:

    
    struct B1 {
      B1( int, int ) {}
    };
    
    struct B2 {
      B2( double, double ) {}
    };
    
    struct D1 : B1 {
      using B1::B1;  // impliclty declare D1( int, int ) {}
      int x;
    };
    
    struct D2 : B2 {
      using B2::B2; // impliclty declare D2( double, double ) {}
      B1 b;
    };
    
    
    D1 has an implicitly declared constructor D1(int, int) that will forward to B1( int, int) if called, and will default initialize x. As x is a POD in this case, initialization will do nothing. Class D1 also has an implicitly declared default constructor, as the forwarding constructor is not user-declared.

    B2 has an implicitly declared constructor D2( double, double ). However, a program that attempts to use this will be ill-formed, as data member b cannot be default initialized.

    
    template< class T >
    struct D : T {
      using T::T;
      ~D() { clog << "Destroying wrapper" << endl; }
    };
    
    
    This example will wrap any type T and forward to all its constructors, while writing a message to the standard log whenever such an object is destroyed.