Defaulted and Deleted Functions (original) (raw)
ISO/IEC JTC1 SC22 WG21 N2210 = 07-0070 - 2007-03-11
Lawrence Crowl
We propose a syntax and semantics for explicitly using the default definition of a function. We also propose a syntax and semantics for deleting the definition of an otherwise visible function.
- Problem Description
- Prior Approaches
- Proposed Solution
- Use Case - Atomic Types
- Changes to the Standard
Problem Description
By default, C++ provides four default special member functions:
- destructor
- default constructor
- copy constructor
- copy assignment
operator =
Users may override these defaults.
Also by default, C++ applies several global operators to classes:
- sequence
operator ,
- address-of
operator &
- indirection
operator *
- member access
operator ->
- member indirection
operator ->*
- free-store allocation
operator new
- free-store deallocation
operator delete
Users may provide class-specific operators.
The management of defaults has several problems:
- Constructor definitions are coupled; declaring any constructor suppresses the default constructor.
- The destructor default is inappropriate to polymorphic classes, requiring an explicit definition.
- Once a default is suppressed, there is no means to resurrect it.
- Default implementations are often more efficient than manually specified implementations.
- Non-default implementations are non-trivial, which affects type semantics, e.g. makes a type non-POD.
- There is no means to prohibit a special member function or global operator without declaring a (non-trivial) substitute.
The most common encounter with these problems is when disabling copying of a class. The accepted technique is to declare a private copy constructor and a private copy assignment operator, and then fail to define either. Unfortunately, the type becomes non-POD and its default constructor must be explicitly written. Further, while diagnostics for non-friend uses are relatively straightforward, the diagnostics for friend uses are deferred to link time and a cryptic a missing symbol message.
The specific problem of concisely ensuring that a class is non-copyable has received attention from Boost. See thedocumentation andheaderfor the boost::noncopyable mixin. This mixin works, though not with entirely satisfactory results. First, the mechanism uses a base class, which under the current language makes the class non-POD. Second, the error messages can be somewhat misleading.
Any successful solution to these problems must be relatively easy to learn and implement.
Prior Approaches
The problems of defaults has been addressed via three primary approaches, as described below. These approaches have significant overlap, but none provide all of the capabilities of any of the others.
Explicit Classes and Default Definitions
In N1717 explicit class and default definitions, Francis Glassborow and Lois Goldthwaite define syntax to indicate that a class has none of the four default special member functions and then to explicitly resurrect the desired defaults. The paper proposed no change to the default application of global operators.
The explicit class approach has a significant syntactic burden for removing a single special member function -- remove them all and rewrite the declarations for the others. This approach encourages a view that explicit classes are inherently different, rather than a restriction of regular classes.
The paper states that
Special member functions which have been explicitly declared and default-defined are never trivial. Therefore an explicit class can never be a POD, even if its special member functions are default-defined. (The justification for this restriction is that the semantics of a class should not change if its inline default-defined functions are moved out of line.)
However, this is to much of a restriction. An inline default-defined function should be trivial if and only if the implicit definition would be trivial. Of course, non-inline definitions should be non-trivial. In essence, we agree with the justification, but not the restriction. Fortunately, subsequent work on the definition of PODs by Beman Dawes in N2172 POD's Revisited; Resolving Core Issue 568 (Revision 2)) provides tools to revisit this issue.
The suggested syntax for explicit defaults permits simultaneous specification of a member initializer list, which is clearly not right. Rather than add additional restrictive text, a restrictive syntax is preferable.
The paper left incomplete the specification that an explicit class without a copy constructor is deemed to have an inaccessible copy constructor. This specification was intended to avoid reverse slicing, where only a sub-object is initialized. The situation is similar for copy assignment operators.
List of Class Defaults
In N1890 Initialization and Initializers section 7, as elaborated in the draft Control of class defaults, Bjarne Stroustrup describes a syntax for controlling defaults, with more goals thanN1717. These goals include not only controlling member defaults, but also controlling the application of global operators, other possible default implementations, and control of inheritance.
The paper's proposed syntax is very terse, and requires a mental mapping from the short codes of the default control list to the actual functions. For the most part, this mapping is straightforward, but still must be memorized. Further, its different syntactic style will slow adoption by users and compiler vendors.
In contrast toN1717, the paper provides no mechanism for to define a non-inline member using the default definition.
The paper also includes mechanisms for controllingclass defaults, which essentially involve inheritance.
- Member functions are non-virtual by default.
- Member functions are non-pure by default.
- Classes are inheritable by default.
- Virtual member functions are overridable by default. In the current language: member functions are easily made virtual, but only individually; it is possible to prevent inheritance of a class, but not also allocate variables of that class; and there is no mechanism to prevent virtual overriding. This paper addresses these inheritance issues as well.
Prohibited Access Specifier
In N2123 Adding the prohibited access specifier to C++09Alisdair Meredith describes a prohibited
access specifier that disables undesired operations within classes. This notion was first introduced by Daveed Vandevoorde in N1964 Modules in C++ (revision 3).
The proposal also enables defining prohibited conversions via declaration of a prohibited overload. This aspect of the proposal is probably the most significant, as it extends into user-defined operations and addresses a major weakness in C++, agressive conversion.
The technique works because the prohibited functions are still visible. Syntactically, the specifier also applies to other data members, for which it does not make sense and requires additional rules. A more precise mechanism is desirable.
Furthermore, the access specifier does not work for free functions. This missing feature seems like such an obvious next step that its absence will be felt.
The prohibited
keyword is new, which will break some programs. A Google code search yields about 500 hits. Sensibly reusing an existing keyword will not affect existing programs.
UnlikeN1717, the paper does not propose a means to specify non-inline default implementations. UnlikeN1890, the paper does not propose a means to specify default implementations for functions that have no implicit default.
Proposed Solution
We propose to define two new mechanisms to explicitly use the default definition of a function and to delete a function definition. This solution
- provides for full control of special member function defaults,
- provides maximally trivial special member function definitions (thus obtaining maximum benefit fromN2172),
- does not require modification of class syntax,
- does not introduce new keywords,
- does not apply syntactically to data, and
- applies to free functions as well as member functions.
Syntactically, the proposal uses=default;
and =delete;
in alternate rules for function-definition. We considered the formsdefault;
, as suggested byN1717, and{default}
, as adopted byN1717, but chose not to use them. The reasons are that the former introduces more choice points into the parse of declarations, while the second introduces either more choice points in the parse of function blocks or substantial lookahead to determine that the syntax{default}
is not a function block.
Default Function Definition
The definition form =default;
indicates that the function's default definition should be used. As expected, this works when the definition is inside the class body, but it also works when the definition is outside the class body, but as with the current language, you must declare the function within the class body.
struct type
{
type() = default; // commonly specified redundancy is now efficent
virtual ~type(); // virtual requires a declaration
type( const type & ); // a simple declaration, but
};
inline type::type( const type & ) = default; // now efficient
type::~type() = default; // a non-inline default definition
Trivial Definitions
Critical to exploiting the benefits ofN2172, an inline and explicitly defaulted definition is trivial if and only if the implicit definition would have been trivial. The potential concern with out-of-class definition is How is the compiler to know if it is trivial?. The existing rule that an inline definition must occur before any use suffices here as well.
struct type
{
type() = default; // trivial
virtual ~type() = default; // non-trivial because virtual
type & operator =( const type & ); // declaration and....
};
inline // the inline definition keeps it trivial
type & type::operator =( const type & ) = default;
Just as important, a non-inline and explicitly defaulted definition is not trivial.
struct type
{
type( const type & ); // declaration and....
};
type::type() = default; // the non-inline makes it non-trivial
This rule enables efficient execution and concise definition while enabling a stable binary interface to an evolving implementation. That is, the machine-level calling sequence remains unchanged even when the definition changes to a non-default. For example, consider the header
type.h : struct type { int x; type(); };
and two (mutually exclusive) definitions as the software evolves,
type1.cc : type::type() = default;
type2.cc : type::type() { x = 3; }
In some cases, the class body can change without requiring a change in member function definition because the default changes with declaration of additional members.
Exception Specification
Unlike the implicit default, the explicit default has normal exception specification semantics. This inconsistency seems at the surface to be undesireable. The reason for the change in semantics is twofold. First, if the explicit default used the implicit exception specification, there would be no syntax to get back to a throws anything state. Second, the users of a function cannot tell if it is defaulted when it is non-inline, as in the example above. It would be undesirable to have semantics of use change due to a change in implementation. So, the explicit declaration needs to have the the normal exception semantics. Users desiring precisely the same exception specification as the implicit declaration must specify it.N1717reached the same conclusion.
Non-Implicit Defaults
So far, this proposal has shown how to make explicit the implicit default definition. There is another opportunity, which is to use a default implementation that is not implicit. For example, consider the equality operator. There is no default equality operator, but an explicit default definition would be able to use a standard-defined, if not implicit, default definition.
struct type
{
bool operator ==( const & type ) = default;
bool operator !=( const & type ) = default;
};
Indeed, there are several potential operations that could have non-implicit default implementations. However, this paper proposes none. We believe that at this point in the standards process, such defaults are best left to a technical report. However, should such a technical report be forthcoming, the syntax is ready.
Delete Function Definition
The definition form =delete;
indicates that the function may not be used. However, all lookup and overload resolution occurs before the deleted definition is noted. That is, it is the definition that is deleted, not the symbol; overloads that resolve to that definition are ill-formed.
The primary power of this approach is twofold. First, use of default language facilities can be made an error by deleting the definition of functions that they require. Second, problematic conversions can be made an error by deleting the definition for the offending conversion (or overloaded function).
This approach of checking for a delete definition late has two benefits. First, it achieves the goal of making a bad overload visible. Second, it is relativley easy to implement, requiring no change to the already complicated lookup and overload rules.
The deleted definition of a function must be its first declaration. This rule prevents inconsistency in interpretation.
The deleted definition mechanism is orthogonal to access specifiers, though accessibility is somewhat moot if the function has been deleted.
Deleted functions are trivial. This rule is necessary to obtain maximum benefit fromN2172.
One can define a template with a deleted definition. Specialization and argument deduction occur as they would with a regular template function.
Disabling Language Features
The canonical example of disabling language features is disabling copying. Simply declare the copy assignment operator and the copy constructor with deleted definitions. Because a declaration of any constructor disables the default constructor, a programmer may choose to add it back with its default definition.
struct type
{
type & operator =( const type & ) = delete;
type( const type & ) = delete;
type() = default;
};
We avoid the problem of reverse slicing identified inN1717because the declaration for a deleted definition still hides the base-class copy constructor.
A more subtle example involves indirectly controlling the allocation of a type. Deleting the definition of a class-specific operator new
will prevent allocation in free store because new
expressions involving type
will be ill-formed.
struct type {
void * operator new( std::size_t ) = delete;
};
In contrast, deleting the definition of a destructor will require allocation in free store because static and automatic variables implicitly call the destructor.
struct type {
~type() = delete; // disable destructor
};
Unfortunately, the approach also prevents deleting a free-store object, thus either limiting its use to singletons or requiring the employment of a garbage collector.
Disabling Undesired Conversions
Removing dangerous conversions is as important as removing undesired language defaults.
struct type
{
type( long long ); // can initialize with an long long
type( long ) = delete; // but not anything less
};
extern void bar( type, long long ); // and the same for bad overloads
void bar( type, long ) = delete; // of free functions
Of considerable note is the interaction of explicit constructors and explicit conversion operators with deleted definitions. (Lois Goldthwaite describes explicit conversion operators in N1592 Explicit Conversion Operators. Michael Wong is drafting core language wording.) The short answer is that the two facilities are orthogonal,explicit
controls the set of functions considered and delete
comments on the final choice. The more helpful answer is a bit more subtle. For example, consider
struct type
{
type( long long );
explicit type( long ) = delete;
};
extern void function( type );
Under this proposal, lookup and the semantics of explicit
are unchanged, so in the overload resoultion for type(42)
, 42 promotes to long
in the overload resolution, and a deleted function is selected, which is an error.
function( type( 42 ) ); // error 42 promotes to long
function( 42 ); // okay type(long long); type(long) not considered
In practice, explicit
and delete
will probably not be used together. Programmers desiring more restrictive diagnostics will use delete
alone rather than explicit
.
Comparison to Prior Approaches
Our solution provides all known strengths ofN1717 andN2123while avoiding many of the identified weakness. In particular, the explicit
class qualifier and the prohibited
access specifier would no longer be necessary.
The solution addresses most of the goals ofN1890(7), specifically those related to function definitions, but not those related to class defaults of inheritance. We have chosen not to address class defaults for the following reasons.
- Member functions can be specified virtual or pure with very little additional syntax. We do not believe that the syntactic savings justifies having to look in two places for the full declaration, which is particularly problematic for visual development environments that wish to warp the screen to the definition.
- Class inheritance and blocking virtual overrides is better handled by some form of final syntax or attribute. The current proposal is focussed on alternate function definitions, and should leave other problems to other papers. Our proposed syntax is more tedious than that ofN1890(7), but also more general, applying to user-defined member and free functions as well.
Use Case - Atomic Types
An effective and correct definition of the atomic types in N2145 C++ Atomic Types and Operationsis a motivating use case for the proposal.
The following code, as defined in this proposal and in conjunction withN2172, appears to satisfy the requirements ofN2145, setting aside the issue of static initialization and inheritance.
typedef struct atomic_int { #ifdef __cplusplus // destructor implicitly declared and defined atomic_int() = default; // otherwise suppressed by other constructors atomic_int( int ); // construct from value int operator =( int __v__ ) volatile; // assign from value atomic_int( const atomic_int & ) = delete; // too dangerous atomic_int & operator =( const atomic_int & ) = delete; // also operator int(); // load the value private: #endif int __f__; } atomic_int;
The destructor is implicitly defaulted and trivial. The default constructor is explicitly defaulted and trivial. The copy constructor and copy assignment operators are deleted and therefore trivial. As a result, the class is trivial and a POD, and may therefore be used in static, aggregate initialization.
The erroneous defaults for copying are deleted, preventing their use. One can still copy from one atomic to another, but only going through a non-atomic value, which makes clear that a copy between two atomics is not itself atomic.
atomic_int w = { 3 }; // static initialization with trivial members
atomic_int x; // default zero initialization
void function( atomic_int * p ) {
atomic_int y( *p ); // error copy constructor is deleted
atomic_int z( int(*p) ); // okay copy construct through value
*p = x; // error copy assignment is deleted
*p = int(x); // okay copy assignment through value
}
Changes to the Standard
We propose the following changes to the standard. The base document is N2134 Working Draft, Standard for Programming Language C++.
8.4 Function definitions [dcl.fct.def]
In paragraph 1, edit
Function definitions have the form
function-definition:
decl-specifier-seqoptdeclarator ctor-initializeroptfunction-body
decl-specifier-seqoptdeclarator function-try-block
decl-specifier-seqoptdeclarator
= default ;
decl-specifier-seqoptdeclarator
= delete ;
Add a new paragraph 7.
A function definition of
=default;
, called an explicitly defaulted definition, has exactly the implementation of an implicit default definition (12.1, 12.4, 12.8). [ Note: Member functions with explicitly defaulted definitions outside the class definition must also be declared within the class definition. — end note ] Unlike with an implicit default definition, the explicitly defaulted definition may be non-inline. [ _Note:_Non-inline definitions are non-trivial (12.1, 12.4, 12.8). This rule permits efficient execution and concise definition while enabling a stable binary interface to an evolving implementation. — end note ] [ Example:
struct sometype { sometype(); sometype( const sometype & ) = default; virtual ~sometype() = 0; }; sometype::sometype() = default; inline sometype::~sometype() = default;
— end example ]
Add a new paragraph 8.
A function definition of
=delete;
, called a deleted definition, makes the function unusable. If a overload resolution matches a deleted definition, the program is ill-formed, diagnostic required. A defaulted definition of a function shall be the first declaration of the function, otherwise, the program is ill-formed, diagnostic required. [ _Example:_One can enforce non-default initialization and non-integral initialization withstruct sometype { sometype() = delete ; sometype( long long ) = delete; sometype( double ); };
```` — end example ] If the implicit invocation of a function uses a deleted definition, the program is ill-formed, diagnostic required. [ _Example:_One can prevent use of a class in
new
expressions with a deleted definition of a user-declaredoperator new
for that class.struct sometype { void * operator new( std::size_t ) = delete; }; sometype * p = new sometype; // error, deleted class operator new
— _end example_ ]
````
``` `` ### 12.1 Constructors [class.ctor]
Within paragraph 5, edit
A default constructor is _trivial_if it is implicitly-declared, or explicitly defaulted and inline,and if:
Within paragraph 7, edit
The implicitly-defined or explicitly-defaulted default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class with an empty mem-initializer-list (12.6.2) and an empty function body. If that user-written default constructor would be ill-formed, the program is ill-formed.
12.4 Destructors [class.dtor]
Within paragraph 3, edit
A destructor is trivial if it is implicitly-declared, or explicitly defaulted, non-virtual and inline, and if:
Within paragraph 5, edit
An implicitly-declared destructor is implicitly defined when it is used to destroy an object of its class type (3.7). A program is ill-formed if the class for which a destructor is implicitly defined or explicitly defaulted has:
- a non-static data member of class type (or array thereof) with an inaccessible or deleted destructor, or
- a base class with an inaccessible or deleted destructor.
12.8 Copying class objects [class.copy]
Within paragraph 6, edit
A copy constructor for class
X
is trivial if it is implicitly declared, or explicitly defaulted and inline,and if
Within paragraph 7, edit
A program is ill-formed if the class for which a copy constructor is implicitly defined, or explicitly defaulted,has:
- a non-static data member of class type (or array thereof) with an inaccessible, deleted, or ambiguous copy constructor, or
- a base class with an inaccessible, deleted, or ambiguous copy constructor.
Within paragraph 8, edit
The implicitly-defined or explicitly-defaulted copy constructor for class
X
performs a memberwise copy of its subobjects.
Within paragraph 11, edit
A copy assignment operator for class
X
is trivial if it is implicitly declared, or explicitly defaulted, non-virtual and inline,and if
In paragraph 12, edit
An implicitly-declared copy assignment operator is implicitly defined when an object of its class type is assigned a value of its class type or a value of a class type derived from its class type. A program is ill-formed if the class for which a copy assignment operator is implicitly definedor explicitly defaulted, has:
- a non-static data member of const type, or
- a non-static data member of reference type, or
- a non-static data member of class type (or array thereof) with an inaccessible or deleted copy assignment operator, or
- a base class with an inaccessible copy assignment operator. Before the implicitly-declared copy assignment operator for a class is implicitly defined, all implicitly-declared copy assignment operators for its direct base classes and its non-static data members shall have been implicitly defined. [ Note: an implicitly-declared copy assignment operator has an exception-specification (15.4). — end note ]
Within paragraph 13, edit
The implicitly-defined or explicitly-defaultedcopy assignment operator for class X performs memberwise assignment of its subobjects. .... It is unspecified whether subobjects representing virtual base classes are assigned more than once by the implicitly-definedor explicitly-defaulted copy assignment operator.
Within paragraph 14, edit
A program is ill-formed if the copy constructor or the copy assignment operator for an object is implicitly used and the special member function is deleted or not accessible (clause 11).
`` ```