Not so Trivial Issues with Trivial

ISO/IEC JTC1 SC22 WG21 N2762 = 08-0272 - 2008-09-18

Lawrence Crowl, Lawrence@Crowl.org, crowl@google.com
Jens Maurer, Jens.Maurer@gmx.net
William Michael Miller

This document is a revision of N2749 = 08-0259 - 2008-08-24. It merges in core issue 509 "Dead code in the specification of default initialization". The issue 509 edits removes duplicate wording and merges it into the definition of "default initialization".

Problem

The original formulation of deleted functions (N2210) defined deleted special member functions to be trivial. The intent was to not change the efficiency features associated with trivial types simply because use of a function is prohibited. However, the adopted formulation (N2346) made deleted special member functions non-trivial.

The consequence of non-trivial deleted functions is that many desirable efficiency features defined for trivial types no longer apply. For example, deleting a default constructor made a class non-trivial, which in turn no longer guaranteed static initialization. Indeed, there is no reason that a non-deleted, non-trivial default constructor should necessarily affect static initialization either. The problem extends to other features, including object lifetime, parameter passing, and memcpy.

The core of the problem is that many features are defined for trivial types when they generally rely on only a subset of the attributes that define a trivial type.

Solution

We propose to redefine those features that are defined with trivial types to be defined with the set of attributes crucial to the feature. Features already defined directly in terms of attributes need no change. This proposal is a continuation in the work of decomposing POD types and their features that began with N2342 POD's Revisited; Resolving Core Issue 568 (Revision 5).

In our solution, we introduce the notion of a trivially copyable type. We then apply this notion in several places. We have chosen a conservative definition for trivally copyable; at lease one weaker form exists.

With the introduction of trivally copyable types, and the redefinition of features on type attributes, there are very few remaining uses of trivial types in the standard. We recommend that any new uses be very carefully considered and weaker types are likely to be preferable.

Several core-language issues are closely related. A full resolution of the problems requires resolving these issues as well. These issues are resolved by N2757 or this paper.

We note that the term "trivial default constructor" (12.1p5) used to imply "no user-declared constructor at all" (other than the copy constructor), because otherwise the default constructor would not have been implicitly declared. And not being implicitly declared, must necessarily have been user-defined and hence not trivial. However, with the arrival of defaulted functions that retain their triviality, this is no longer the case. Singling out default constructors as "trivial" and assigning special semantics seems non-intuitive given that any number of other constructors may be defined and used to initialize an object.

Specific cases

In summary, we propose the following changes to the standard.

1.8p5 intro.object:
The presence or absence of user-defined constructors appears not to be material for the "continuous bytes of storage" requirement, but the presence or absence of virtual functions and virtual base classes may be material. Out of an abundance of caution, the other trivial member function restrictions are retained.
3.8 basic.life:
When accessing (parts of) out-of-lifetime objects, the implementation concern is accessing objects with virtual base classes and virtual functions, where the necessary environment setup in the object (e.g. the vtable pointer) may not be available (either not yet or no longer). We restrict the undefined behavior to these cases.
3.9p2,3,4 basic.types:
memcpy provisions and value representations are only concerned with copying and assignment of objects, but not with the issue how an object obtained its initial value. The presence or absence of user-defined constructors will not make a difference.
5.2.2 expr.call:
Passing an object to the ellipsis in a function call is only a problem if the object's copy contruction or destruction is non-trivial, because neither special member function may actually get called for lack of C++ housekeeping in ellipsis functions.
6.7 stmt.dcl:
Jumping over the definition of an automatic variable will pose the problem of whether the destructor for that variable should be run at the end of the block. Thus, the destructor needs to be trivial, i.e. have no effect. Similarly, the default constructor (the one potentially used to initialize the object) is also required to not do anything, i.e. be trivial. No other requirements are necessary.
8.5p5 dcl.init:
Core issue 509 will perform major changes in default initialization, which will benefit the cause of this paper.
12.7p1 class.cdtor:
Similar to the lifetime rules in 3.8 (which may be redundant with this provision), only the specific special member function being executed is inspected.
25.4p4 alg.c.library:
qsort should only be concerned with copying and potentially destruction, not initial values. So limiting the realm of undefined behavior in this case should not be a problem.

Wording

1.8 The C++ object model [intro.object]

Edit paragraph 5 as follows.

An object of trivial trivially copyable or standard-layout type (3.9) shall occupy contiguous bytes of storage.

3.8 Object Lifetime [basic.life]

Edit paragraph 2 as follows.

[Note: the lifetime of an array object or of an object of trivial type (3.9) starts as soon as storage with proper size and alignment is obtained, and its lifetime ends when the storage which the array or object occupies is reused or released. 12.6.2 describes the lifetime of base and member subobjects. —end note]

Edit paragraph 5 as follows.

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated35) [Footnote: For example, before the construction of a global object of non-POD class type (12.7). —end footnote] or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. Such a pointer refers to allocated storage (3.7.3.2), and using the pointer as if the pointer were of type void*, is well-defined. Such a pointer may be dereferenced but the resulting lvalue may only be used in limited ways, as described below. If the object will be or was of a class type with a non-trivial destructor, and the pointer is used as the operand of a delete-expression, the program has undefined behavior. If the object will be or was of a non-trivial class type, the The program has undefined behavior if:

Edit paragraph 6 as follows.

Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any lvalue which refers to the original object may be used but only in limited ways. Such an lvalue refers to allocated storage (3.7.3.2), and using the properties of the lvalue which do not depend on its value is well-defined. If an lvalue-to-rvalue conversion (4.1) is applied to such an lvalue, the program has undefined behavior; if the original object will be or was of a non-trivial class type, the The program has undefined behavior if:

3.9 Types [basic.types]

Edit paragraph 2 as follows:

For any object (other than a base-class subobject) of trivial trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array of char or unsigned char.36) If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value.

Edit paragraph 3 as follows:

For any trivial trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the value of obj1 is copied into obj2, using the std::memcpy library function, obj2 shall subsequently hold the same value as obj1. [Example:

T* t1p;
T* t2p;
// provided that t2p points to an initialized object ...
std::memcpy(t1p, t2p, sizeof(T));
// at this point, every subobject of trivial trivially copyable type in *t1p contains
// the same value as the corresponding subobject in *t2p

end example]

Edit paragraph 4 as follows:

The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T). The value representation of an object is the set of bits that hold the value of type T. For trivial trivially copyable types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values. [Footnote: The intent is that the memory model of C++ is compatible with that of ISO/IEC 9899 Programming Language C. —end footnote]

Edit paragraph 9 as follows.

Arithmetic types (3.9.1), enumeration types, pointer types, pointer to member types (3.9.2), and std::nullptr_t, and cv-qualified versions of these types (3.9.3) are collectively called scalar types. Scalar types, POD classes (clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called POD types. Scalar types, trivially copyable class types (clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called trivially copyable types. Scalar types, trivial class types (clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called trivial types. Scalar types, standard-layout class types (clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called standard-layout types.

5.2.2 Function call [expr.call]

Edit paragraph 7 as follows.

... Passing an argument of non-trivial class type (clause 9) with a non-trivial copy constructor or a non-trivial destructor with no corresponding parameter is conditionally-supported, with implementation-defined semantics. ...

5.3.4 New [expr.new]

Edit paragraph 16 as follows. This edit arises from issue 509.

A new-expression that creates an object of type T initializes that object as follows:

5.19 Constant expressions [expr.const]

Edit paragraph 2 as follows. Note that issue 684 will make non-conflicting edits to this paragraph.

A conditional-expression is a constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2), but subexpressions of logical AND (5.14), logical OR (5.15), and conditional (5.16) operations that are not evaluated are not considered [Note: an overloaded operator invokes a function —end note]:

6.7 Declaration statement [stmt.dcl]

Edit paragraph 3 as follows:

It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps78) from a point where a local variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has trivial type (3.9) scalar type, class type with a trivial default constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the preceding types and is declared without an initializer (8.5).

8.5 Initializers [dcl.init]

Edit paragraph 5 as follows, splitting paragraph 5 into three paragraphs, one for each kind of initialization. The majority of this edit arises from issue 509.

To zero-initialize an object or reference of type T means:

To default-initialize an object of type T means:

If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.

To value-initialize an object of type T means:

Edit paragraph 6 as follows, This edit arises from issue 509.

A program that calls for default-initialization or value-initialization of an entity of reference type is ill-formed. If T is a cv-qualified type, the cv-unqualified version of T is used for these definitions of zero-initialization, default-initialization, and value-initialization.

Edit paragraph 7 as follows, This edit arises from issue 509.

[Note: Every object of static storage duration shall be zero-initialized at program startup before any other initialization takes place. [Note: in In some cases, additional initialization is done later. —end note]

Edit paragraph 9 as follows. The majority of this edit arises from issue 509.

If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, a non-static object has indeterminate value. [Note: Objects with static storage duration are zero-initialized, see 3.6.2 basic.start.init. —end note] and the object is of (possibly cv-qualified) non-trivial class type (or array thereof), the object shall be default-initialized; if the object is of const-qualified type, the underlying class type shall have a user-provided default constructor. Otherwise, if no initializer is specified for a non-static object, the object and its subobjects, if any, have an indeterminate initial value95) [Footnote: This does not apply to aggregate objects with automatic storage duration initialized with an incomplete brace-enclosed initializer-list; see 8.5.1 dcl.init.aggr. —end footnote]; if the object or any of its subobjects are of const-qualified type, the program is ill-formed.

Chapter 9 Classes [class]

Edit paragraph 5 as follows. Note that this edit is subsequent to the application of the edits in core issue 683 in N2757.

A trivially copyable class is a class that:

A trivial class is a class that:

[Note: in particular, a trivially copyable or trivial class does not have virtual functions or virtual base classes. —end note]

9.5 Unions [class.union]

Within paragraph 1 edit as follows.

... [Note: If any non-static data member of a union has a non-trivial default constructor (12.1), copy constructor (12.8), copy assignment operator (12.8), or destructor (12.4), the corresponding member function of the union must be user-declared user-provided or it will be implicitly deleted (8.4) for the union. —end note]

12.4 Destructors [class.dtor]

Edit paragraph 3 as follows.

If a class has no user-declared destructor, a destructor is declared implicitly. An implicitly-declared destructor is an inline public member of its class. If the class is a union-like class that has a variant member with a non-trivial destructor, an implicitly-declared destructor is defined as deleted (8.4). A destructor is trivial if it is not user-provided and if:

12.6.2 Initializing bases and members [class.base.init]

Edit paragraph 4 as follows, This edit arises from issue 509.

If a given non-static data member or base class is not named by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer), then the entity is default-initialized (8.5 dcl.init), unless the entity is a variant member (9.5 class.union), in which case no initialization is performed.

After the call to a constructor for class X has completed, if a member of X is neither specified in the constructor's mem-initializers, nor default-initialized, nor value-initialized initialized, nor given a value during execution of the compound-statement of the body of the constructor, the member has indeterminate value.

12.7 Construction and destruction [class.cdtor]

Edit paragraph 1 as follows.

For an object of non-trivial class type (clause 9), before the constructor begins execution and after the destructor finishes execution, referring to any non-static member or base class of the object results in undefined behavior. For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior. For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes execution results in undefined behavior.