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.
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 inheriting 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 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 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, 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.
Note that typedef names that alias a direct base will work in the same way, but it is not possible to mix and match names either side of the ::.
For example:
The following would all be valid using declarations, and all mean the same thing. Attempting to use any combination of more than one of these would be ill-formed for the same reason that multiple declarations of the same name are ill-formed.struct derived : base { typedef base inherited; };
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. The wording below contains a change to 12.1 class.ctor to prohibit such strange implicitly-declared default constructors.
base_constructors
base_constructors
In a lookup in which the constructor is an acceptable lookup resultChange 7.3.3 namespace.udecl paragraph 1 as follows,and if the nested-name-specifier nominates a class C:
- if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (clause 9), or
- if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id's template-name in the last component of the nested-name-specifier,
, and the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (clause 9)the name is instead considered to name the constructor of class C. [ Note: For example, the constructor is not an acceptable lookup result in an elaborated-type-specifier so the constructor would not be used in place of the injected-class-name. ] Such a constructor name shall be used only in the declarator-id of a declaration that names a constructor or in a using-declaration.
A using-declaration introduces a name into the declarative region in which the using-declaration appears.Change 7.3.3 namespace.udecl paragraph 3 as followsThat name is a synonym for the name of some entity declared elsewhere.
using-declaration: using typenameopt ::opt nested-name-specifier unqualified-id ; using :: unqualified-id ;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 ] 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.inh-ctor); otherwise the name specified in a using-declaration is a synonym for the name of some entity declared elsewhere.
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, the nested-name-specifier shall name a direct base class of the class being defined; otherwise itChange 7.3.3 namespace.udecl paragraph 4 as followsSuch a using-declarationintroduces the set of declarations found by member name lookup (10.2, 3.4.3.1).
[ Note:Change 7.3.3 namespace.udecl paragraph 15 as followssince constructors andSince destructors do not have names,a using-declaration cannot refer toa constructor ora 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 ] ...
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.inh-ctor.]Change 7.3.3 namespace.udecl paragraph 18 as follows
The access rules for inheriting constructors are specified in 12.9 class.inh-ctor; otherwise allChange 7.3.3 namespace.udecl paragraph 19 as followsAllinstances 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. ...
The alias created by the using-declaration has the usual accessibility for a member-declaration. [Note: A using-declaration that names a constructor does not create aliases; see 12.9 class.inh-ctor for the pertinent accessibility rules.]Create a new section 12.9 class.inh-ctor [not shown in bold here]:
12.9 Inheriting Constructors [class.inh-ctor]A using-declaration (7.3.3 namespace.udecl) that names a constructor implicitly declares a set of inheriting constructors as follows: The candidate set of inherited constructors from the class X named in the using-declaration consists of actual constructors and notional constructors that result from the transformation of defaulted parameters as follows:
The constructor characteristics of a constructor or constructor template are
- all non-template constructors of X, and
- for each non-template constructor of X that has at least one parameter with a default argument, the set of constructors that results from omitting any ellipsis parameter specification and successively omitting parameters with a default argument from the end of the parameter-type-list, and
- all constructor templates of X, and
- for each constructor template of X that has at least one parameter with a default argument, the set of constructor templates that results from omitting any ellipsis parameter specification and successively omitting parameters with a default argument from the end of the parameter-type-list.
For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the class where the using-declaration appears. Similarly, for each constructor template in the candidate set of inherited constructors, a constructor template is implicitly declared with the same constructor characteristics unless there is an equivalent user-declared constructor template (14.5.6.1 temp.over.link) in the class where the using-declaration appears. [ Note: Default arguments are not inherited. ]
- the template parameter list (14.1 temp.param), if any,
- the parameter-type-list (8.3.5 dcl.fct),
- the 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).A constructor so declared has the same access as the corresponding constructor in X. It is deleted if the corresponding constructor in X is deleted (8.4 dcl.fct.def).
[Note: Default and copy constructors may be implicitly declared as specified in 12.1 class.ctor and 12.8 class.copy.][Example:
struct B1 { B1( int ); }; struct B2 { B2( int = 13, int = 42 ); }; struct D1 : B1 { using B1::B1; }; struct D2 : B2 { using B2::B2; };
The candidate set of inherited constructors in D1 for B1 is
- B1( const B1 & )
- B1( int )
The set of constructors present in D1 is
- D1() implicitly declared default constructor, ill-formed if used
- D1( const D1 & ) implicitly declared copy constructor, not inherited
- D1( int ) implicitly declared inheriting constructor
The candidate set of inherited constructors in D2 for B2 is
- B2( const B2 & )
- B2( int = 13, int = 42 )
- B2( int = 13 )
- B2()
The set of constructors present in D2 is
- D2() implicitly declared default constructor, not inherited
- D2( const D2 & ) implicitly declared copy constructor, not inherited
- D2( int, int ) implicitly declared inheriting constructor
- D2( int ) implicitly declared inheriting constructor
]
[Note: If two using-declarations declare inheriting constructors with the same signatures, the program is ill-formed (9.2 class.mem, 13.1 over.load), because an implicitly-declared constructor introduced by the first using-declaration is not a user-declared constructor and thus does not preclude another declaration of a constructor with the same signature by a subsequent using-declaration. [Example:
] ]struct B1 { B1( int ); }; struct B2 { B2( 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 ); // ok: user declaration prevents conflict };
An 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 nested-name-specifier of the using-declaration and an expression-list as specified below, and where the compound-statement in its function body is empty (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 formstatic_cast<T&&>(p)
wherep
is the name of the corresponding constructor parameter andT
is the declared type ofp
.[ Example:
struct B1 { B1( int ) {} }; struct B2 { B2( double ) {} }; struct D1 : B1 { using B1::B1; // implicitly declares D1( int ) int x; }; void test() { D1 d(6); // ok; d.x is not initialized D1 e; // error: D1 has no default constructor } struct D2 : B2 { using B2::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 T::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 classD
is destroyed. ]
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, adefaultconstructor having no parameters is implicitly declared. ...