US-65: Removing Inheriting Constructors
Author: Douglas Gregor
Contact: doug.gregor@gmail.com
Organization: Apple
Date: 2011-02-28
Number: N3258=11-0028
Introduction
This proposal recommends the removal of inheriting constructors
(N2540) from the C++0x working paper, per National Body comment
US-65. The author believes that inheriting constructors should be
considered as a feature for future language standards, but that
the risks associated with keeping this feature in the C++0x working
paper far outweigh the benefits.
Inheriting constructors should be removed from the C++0x working
paper for the following reasons:
- Inheriting constructors are unimplemented: Without
implementation experience, we have no reasonable assurance that the
feature is implementable in all compilers or can work as the authors
intended. Implementing a feature is the only way to ensure that the
specification is sound, and has always resulted in changes and
improvements to the specification.
- Inheriting constructors have no user experience: Since
there are no implementations of this feature, there are no users and
has been no feedback about usefulness of the feature. For example,
we don't know whether the limitations of this feature (inability to
provide initialization for other bases or members, inability to
inherit constructors from a dependent base class) are too
restrictive for real-world use,
- Inheriting constructors can be emulated
with variadic templates: by
using variadic templates, one can write a constructor that
captures any arguments and forwards them to a base class,
effectively inheriting that base class's constructors. The
following section contains more information about the differences
between such a forwarding constructor and inheriting
constructors.
- The cost of removing inheriting constructors is low:
There is no code, no language wording, and no Standard Library
construct that depends on inheriting constructors, and the wording
for inheriting constructors is fairly self-contained, making the
removal of this feature simple. Moreover, since this feature has not
been implemented in the more than two years since it was accepted,
it seems unlikely that removal will affect any National Body votes
or the user community at large.
Emulating Inheriting Constructors with Variadic Templates
One can emulate inheriting constructors using a catch-all variadic template that forwards all of its arguments. For example:
template<typename Mixin>
class add_color : public Mixin {
Color color;
public:
template<typename ...Args>
add_color(Args &&...args)
: Mixin(std::forward<Args>(args)...), color(Red) { }
};
There are several benefits to the variadic-template implementation for constructor forwarding:
- Ability to initialize other base classes or members.
- Ability to make changes to the parameters before they are forwarded.
- Ability to add arbitrary code into the subclass's constructor.
There are also several problems with the variadic-template implementation:
- The variadic-template constructor overloads as a catch-all variadic template, rather than participating as the underlying set of base constructors.
- Non-type constructor characteristics such as
explicit
and constexpr
are lost.
The variadic-template implementation is clearly not a drop-in
replacement for inheriting constructors, and there are trade-offs in
both directions. Does that mean we need both features? Perhaps, but we
need usage experience to determine whether the inheriting-constructors
feature as specified has the right design tradeoffs. We may find that
the limitations of the feature as specified mean that the vast
majority of users will end up employing the variadic-template
implementation instead, in which case we'd likely want to change the
design of inheriting constructors. We need this usage experience prior
to standardization, not after standardization, and it becomes even
more important when we already have a partial solution to the
problem.
Proposed Wording
Modify 3.4.3.1 [class.qual]p2 as follows:
- In a lookup in which the constructor is an acceptable lookup result, and 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
- in a using-declaration (7.3.3) that is a member-declaration, 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. -- end note ] Such a constructor name shall be used only in the declarator-id of a declaration that names a constructor or in a using-declaration.
Modify 7.3.3 [namespace.udecl] 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.
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), it implicitly declares a set of constructors in the class in which the using-declaration appears (12.9); otherwise the name specified in a using-declaration is a synonym for the name of some entity declared elsewhere.
- (No change to this paragraph)
- 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 itSuch a using-declaration introduces the set of declarations found by member name lookup (10.2, 3.4.3.1).
- [Note: Since constructors and destructors do not have names, a using-declaration cannot refer to a constructor or 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), cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting). [ Note: For using-declarations that name a constructor, see 12.9. -- end note ]
- (No change to this paragraph)
- The access rules for inheriting constructors are specified in 12.9; otherwise allAll 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 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 for the pertinent accessibility rules. -- end note]
Remove section 12.9 [class.inhctor].
Note: we leave the change to 12.1 [class.ctor]p5, which was a small
clarification that does not have anything to do with inheriting
constructors.
Last modified: Mon Feb 28 08:34:59 PST 2011