Not so Trivial Issues with Trivial

ISO/IEC JTC1 SC22 WG21 N2749 = 08-0259 - 2008-08-24

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

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. That work is in progress.

Wording

The wording in this paper includes unmodified paragraphs. These paragraphs serve to provide context for the changes proposed. We anticipate that a revision for formal voting will remove those unmodified paragraphs.

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.6.2 Initialization of non-local objects [basic.start.init]

Paragraph 1 will be modified by issue 688 to avoid the term "trivial". Issue 684 may also affect this paragraph.

Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. A reference with static storage duration and an object of trivial or literal type with static storage duration can be initialized with a constant expression (5.19 [expr.const]); this is called constant initialization. ...

3.8 Object Lifetime [basic.life]

Leave paragraph 1 unchanged.

The lifetime of an object is a runtime property of the object. The lifetime of an object of type T begins when:

The lifetime of an object of type T ends when:

Edit paragraph 2 as follows.

[Note: the lifetime of an array object or of an object of trivial type (3.9) scalar type or of class type with a trivial default constructor starts as soon as storage with proper size and alignment is obtained, and its. The lifetime of an array object or of an object of scalar type or of class type with a trivial destructor 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]

Leave paragraph 4 unchanged.

A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

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) 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 with virtual bases or virtual member functions, 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 with virtual bases or virtual member functions, the program has undefined behavior if:

Leave paragraph 8 unchanged.

If a program ends the lifetime of an object of type T with static (3.7.1) or automatic (3.7.2) storage duration and if T has a non-trivial destructor,36) the program must ensure that an object of the original type occupies that same storage location when the implicit destructor call takes place; otherwise the behavior of the program is undefined. This is true even if the block is exited with an exception.

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.

Leave paragraph 10 unchanged.

A type is a literal type if it is:

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]

Paragraph 16 will be modified by issue 509 to avoid the term "trivial".

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

5.3.5 Delete [expr.delete]

Leave paragraph 5 unchanged.

If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.

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) a trivial default constructor and a trivial destructor and is declared without an initializer (8.5).

Paragraph 4 will be modified by issue 688 to avoid the term "trivial".

The zero-initialization (8.5) of all local objects with static storage duration (3.7.1) is performed before any other initialization takes place. A local object of trivial or literal type (3.9 [basic.types]) with static storage duration initialized with constant-expressions is initialized before its block is first entered An implementation is permitted to perform early initialization of other local objects with static storage duration under the same conditions that an implementation is permitted to statically initialize an object with static storage duration in namespace scope (3.6.2). Otherwise such an object is initialized the first time control passes through its declaration; such an object is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration. If control re-enters the declaration (recursively) while the object is being initialized, the behavior is undefined.

7.1.5 The constexpr specifier [dcl.constexpr]

Leave paragraph 4 unchanged.

A trivial copy constructor is also a constexpr constructor.

Paragraph 7 will be modified by issue 688.

A constexpr specifier used in an object declaration declares the object as const. Such an object shall be initialized, and every expression that appears in its initializer (8.5 [dcl.init]) shall be a constant expression. Every implicit conversion used in converting the initializer expressions and every constructor call used for the initialization shall be one of those allowed in a constant expression (5.19 [expr.const]) ...

8.4 Function definitions [dcl.fct.def]

Leave paragraph 9 unchanged.

... A special member function is user-provided if it is user-declared and not explicitly defaulted on its first declaration. ...


struct trivial {
  trivial() = default;
  trivial(const trivial&) = default;
  trivial& operator =(const trivial&) = default;
  ~trivial() = default;
};

...

8.5 Initializers [dcl.init]

Edit paragraph 5 as follows. This paragraph will also be modified by issue 509.

To default-initialize an object of type T means:

Edit paragraph 9 as follows. This paragraph will also be modified by issue 509.

If no initializer is specified for an object, and the object is of (possibly cv-qualified) non-trivial class type (or array thereof) has a non-trivial default constructor, 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); if the object or any of its subobjects are of const-qualified type, the program is ill-formed.

8.5.1 Aggregates [dcl.init.aggr]

Paragraph 14 will be heavily modified by issue 688 and thus avoid the term "trivial".

When an aggregate with static storage duration is initialized with a brace-enclosed initializer-list, if all the member initializer expressions are constant expressions, and the aggregate is a trivial type, the initialization shall be done during the static phase of initialization (3.6.2 [basic.start.init]); otherwise, it is unspecified whether the initialization of members with constant expressions takes place during the static phase or during the dynamic phase of initialization.

Chapter 9 Classes [class]

Edit paragraph 5 as follows.

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]

Leave paragraph 9 unchanged.

A POD struct is a class that is both a trivial class and a standard-layout class, and has no non-static data members of type non-POD struct, non-POD union (or array of such types). Similarly, a POD union is a union that is both a trivial class and a standard layout class, and has no non-static data members of type non-POD struct, non-POD union (or array of such types). A POD class is a class that is either a POD struct or a POD union.

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.1 Constructors [class.ctor]

Leave paragraph 5 unchanged.

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, a default constructor having no parameters is implicitly declared. An implicitly-declared default constructor is an inline public member of its class. For a union-like class that has a variant member with a non-trivial default constructor, an implicitly-declared default constructor is defined as deleted (8.4). A default constructor is trivial if it is not user-provided (8.4) and if:

12.2 Temporary objects [class.temporary]

Leave paragraph 3 unchanged.

When an implementation introduces a temporary object of a class that has a non-trivial constructor (12.1, 12.8), it shall ensure that a constructor is called for the temporary object. Similarly, the destructor shall be called for a temporary with a non-trivial destructor (12.4). Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception. The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression.

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]

Paragraph 4 will be modified by issue 509 to avoid use of "trivial".

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

an implicitly-declared copy constructor is defined as deleted (8.4).

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 constructor begins 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.

12.8 Copying class objects [class.copy]

Leave paragraph 4 unchanged.

If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class is a union-like class that has a variant member with a non-trivial copy constructor, an implicitly-declared copy constructor is defined as deleted (8.4). Thus, for the class definition ...

Leave paragraph 6 unchanged.

A copy constructor for class X is trivial if it is not user-provided (8.4) and if

otherwise the copy constructor is non-trivial.

Leave paragraph 10 unchanged.

If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. If the class is a union-like class that has a variant member with a non-trivial copy assignment operator, an implicitly-declared copy assignment operator is defined as deleted (8.4). The implicitly-declared copy assignment operator for a class X will have the form ...

Leave paragraph 11 unchanged.

A copy assignment operator for class X is trivial if it is not user-provided and if

otherwise the copy assignment operator is non-trivial.

20.5.2 Header <type_traits> synopsis [meta.type.synop]

The is_trivial and has_trivial_* traits are unchanged.

20.6.17 Class template reference_closure [func.referenceclosure]

Leave paragraph 3 unchanged.

The instances of reference_closure class template are trivial and standard-layout classes (3.9).

25.4 C library algorithms [alg.c.library]

Leave paragraph 4 unchanged. It makes qsort undefined if the type is not trivial.

Chapter 29 Atomic operations library [atomics]

Chapter 29 was the original motivation for this propsal. With the proposed changes in the core language, no change is needed in this chapter.