[basic] (original) (raw)
6 Basics [basic]
6.1 Preamble [basic.pre]
[Note 1:
This Clause presents the basic concepts of the C++ language.
It explains the difference between an object and a name and how they relate to the value categories for expressions.
It introduces the concepts of a declaration and a definition and presents C++'s notion of type, scope, linkage, and storage duration.
The mechanisms for starting and terminating a program are discussed.
Finally, this Clause presents the fundamental types of the language and lists the ways of constructing compound types from these.
— _end note_]
[Note 2:
This Clause does not cover concepts that affect only a single part of the language.
Such concepts are discussed in the relevant Clauses.
— _end note_]
Two names are the same if
- they are identifiers composed of the same character sequence, or
- they are conversion-function-ids formed with equivalent ([temp.over.link]) types, or
- they are operator-function-ids formed with the same operator, or
- they are literal-operator-ids formed with the same literal suffix identifier.
Every name is introduced by a declaration, which is a
- name-declaration,block-declaration, ormember-declaration ([dcl.pre], [class.mem]),
- init-declarator ([dcl.decl]),
- identifierin a structured binding declaration ([dcl.struct.bind]),
- identifierin a result-name-introducerin a postcondition assertion ([dcl.contract.res]),
- init-capture ([expr.prim.lambda.capture]),
- condition with a declarator ([stmt.pre]),
- member-declarator ([class.mem]),
- using-declarator ([namespace.udecl]),
- parameter-declaration ([dcl.fct], [temp.param]),
- type-parameter ([temp.param]),
- type-tt-parameter ([temp.param]),
- variable-tt-parameter ([temp.param]),
- concept-tt-parameter ([temp.param]),
- elaborated-type-specifierthat introduces a name ([dcl.type.elab]),
- class-specifier ([class.pre]),
- enum-specifier orenumerator-definition ([dcl.enum]),
- exception-declaration ([except.pre]), or
- implicit declaration of an injected-class-name ([class.pre]).
[Note 4:
Some names denote types or templates.
In general, whenever a name is encountered it is necessary to look it up ([basic.lookup]) to determine whether that name denotes one of these entities before continuing to parse the program that contains it.
— _end note_]
A variable is introduced by the declaration of a reference other than a non-static data member or of an object.
The variable's name, if any, denotes the reference or object.
An entity is a value, object, reference, structured binding, result binding, function, enumerator, type, class member, bit-field, template, template specialization, namespace, or pack.
An entity E is denoted by the name (if any) that is introduced by a declaration of E or by a typedef-name introduced by a declaration specifying E.
A name used in more than one translation unit can potentially refer to the same entity in these translation units depending on thelinkage of the name specified in each translation unit.
6.2 Declarations and definitions [basic.def]
A declaration ([dcl]) may (re)introduce one or more names and/or entities into a translation unit.
If so, the declaration specifies the interpretation and semantic properties of these names.
A declaration of an entity or typedef-name X is a redeclaration of Xif another declaration of X is reachable from it ([module.reach]); otherwise, it is a first declaration.
Each entity declared by a declaration is also defined by that declaration unless:
- it declares a function without specifying the function's body ([dcl.fct.def]),
- it contains theextern specifier ([dcl.stc]) or alinkage-specification15 ([dcl.link]) and neither an initializer nor afunction-body,
- it declares a non-inline static data member in a class definition ([class.mem], [class.static]),
- it declares a static data member outside a class definition and the variable was defined within the class with the constexprspecifier ([class.static.data]) (this usage is deprecated; see [depr.static.constexpr]),
- it is an elaborated-type-specifier ([class.name]),
- it is anopaque-enum-declaration ([dcl.enum]),
- it is atemplate-parameter ([temp.param]),
- it is aparameter-declaration ([dcl.fct]) in a functiondeclarator that is not the declarator of afunction-definition,
- it is atypedef declaration ([dcl.typedef]),
- it is an alias-declaration ([dcl.typedef]),
- it is ausing-declaration ([namespace.udecl]),
- it is a deduction-guide ([temp.deduct.guide]),
- it is a static_assert-declaration ([dcl.pre]),
- it is anattribute-declaration ([dcl.pre]),
- it is anempty-declaration ([dcl.pre]),
- it is a using-directive ([namespace.udir]),
- it is a using-enum-declaration ([enum.udecl]),
- it is a template-declaration ([temp.pre]) whose template-head is not followed by either a concept-definition or a declarationthat defines a function, a class, a variable, or a static data member,
- it is an explicit instantiation declaration ([temp.explicit]), or
- it is an explicit specialization whosedeclaration is not a definition.
A declaration is said to be a definition of each entity that it defines.
[Example 1:
All but one of the following are definitions:int a; extern const int c = 1; int f(int x) { return x+a; } struct S { int a; int b; }; struct X { int x; static int y; X(): x(0) { } };int X::y = 1; enum { up, down }; namespace N { int d; } namespace N1 = N; X anX; whereas these are just declarations:extern int a; extern const int c; int f(int); struct S; typedef int Int; extern X anotherX; using N::d;
— _end example_]
[Example 2:
Given#include <string> struct C { std::string s; };int main() { C a; C b = a; b = a;} the implementation will implicitly define functions to make the definition of C equivalent tostruct C { std::string s; C() : s() { } C(const C& x): s(x.s) { } C(C&& x): s(static_caststd::string&&\(x.s)) { } C& operator=(const C& x) { s = x.s; return *this; } C& operator=(C&& x) { s = static_caststd::string&&\(x.s); return *this; } ~C() { } };
— _end example_]
In the definition of an object, the type of that object shall not be an incomplete type ([basic.types.general]), an abstract class type ([class.abstract]), or a (possibly multidimensional) array thereof.
6.3 One-definition rule [basic.def.odr]
No translation unit shall contain more than one definition of any definable item.
An expression or conversion is potentially evaluated unless it is an unevaluated operand ([expr.context]), a subexpression thereof, or a conversion in an initialization or conversion sequence in such a context.
The set of potential results of an expression E is defined as follows:
- If E is a subscripting operation with an array operand, the set contains the potential results of that operand.
- If E is a class member access expression ([expr.ref]) of the form . template naming a non-static data member, the set contains the potential results of .
- If E is a class member access expression naming a static data member, the set contains the id-expression designating the data member.
- If E has the form (), the set contains the potential results of .
- If E is a glvalue conditional expression, the set is the union of the sets of potential results of the second and third operands.
- If E is a comma expression, the set contains the potential results of the right operand.
- Otherwise, the set is empty.
[Note 1:
This set is a (possibly-empty) set of id-expressions, each of which is either E or a subexpression of E.
[Example 1:
In the following example, the set of potential results of the initializer of n contains the first S::x subexpression, but not the secondS::x subexpression.
struct S { static const int x = 0; };const int &f(const int &r);int n = b ? (1, S::x) : f(S::x); — _end example_]
— _end note_]
A function is named byan expression or conversion as follows:
- A function is named by an expression or conversion if it is the selected member of an overload set ([basic.lookup], [over.match], [over.over]) in an overload resolution performed as part of forming that expression or conversion, unless it is a pure virtual function and either the expression is not an id-expression naming the function with an explicitly qualified name or the expression forms a pointer to member ([expr.unary.op]).
[Note 2:
A constructor selected to copy or move an object of class type is considered to be named by an expression or conversion even if the call is actually elided by the implementation ([class.copy.elision]).
— _end note_] - A deallocation function for a class is named by a new-expression if it is the single matching deallocation function for the allocation function selected by overload resolution, as specified in [expr.new].
A variable is named by an expression if the expression is an id-expression that denotes it.
A variable x that is named by a potentially-evaluated expression Nthat appears at a point Pis odr-used by N unless
- x is a reference that is usable in constant expressions at P ([expr.const]) or
- N is an element of the set of potential results of an expression E, where
- E is a discarded-value expression ([expr.context]) to which the lvalue-to-rvalue conversion is not applied or
- x is a non-volatile object that is usable in constant expressions at P and has no mutable subobjects and
* E is a class member access expression ([expr.ref]) naming a non-static data member of reference type and whose object expression has non-volatile-qualified type or
* the lvalue-to-rvalue conversion ([conv.lval]) is applied to E andE has non-volatile-qualified non-class type
[Example 2: int f(int);int g(int&);struct A { int x;};struct B { int& r;};int h(bool cond) { constexpr A a = {1};constexpr const volatile A& r = a; int _ = f(cond ? a.x : r.x); int x, y;constexpr B b1 = {x}, b2 = {y}; int _ = g(cond ? b1.r : b2.r); int _ = ((cond ? x : y), 0); return [] { return b1.r; }();} — _end example_]
A structured binding is odr-used if it appears as a potentially-evaluated expression.
*this is odr-used if this appears as a potentially-evaluated expression (including as the result of any implicit transformation to a class member access expression ([expr.prim.id.general])).
A virtual member function is odr-used if it is not pure.
A function is odr-used if it is named by a potentially-evaluated expression or conversion.
A non-placement allocation or deallocation function for a class is odr-used by the definition of a constructor of that class.
A non-placement deallocation function for a class is odr-used by the definition of the destructor of that class, or by being selected by the lookup at the point of definition of a virtual destructor ([class.dtor]).16
An assignment operator function in a class is odr-used by an implicitly-defined copy assignment or move assignment function for another class as specified in [class.copy.assign].
A constructor for a class is odr-used as specified in [dcl.init].
A local entity ([basic.pre]) is odr-usable in a scope ([basic.scope.scope]) if
- either the local entity is not *this, or an enclosing class or non-lambda function parameter scope exists and, if the innermost such scope is a function parameter scope, it corresponds to a non-static member function, and
- for each intervening scope ([basic.scope.scope]) between the point at which the entity is introduced and the scope (where *this is considered to be introduced within the innermost enclosing class or non-lambda function definition scope), either
- the intervening scope is a block scope,
- the intervening scope is a contract-assertion scope ([basic.scope.contract]),
- the intervening scope is the function parameter scope of a lambda-expression or requires-expression, or
- the intervening scope is the lambda scope of a lambda-expressionthat has a simple-capturenaming the entity or has a capture-default, and the block scope of the lambda-expressionis also an intervening scope.
If a local entity is odr-used in a scope in which it is not odr-usable, the program is ill-formed.
[Example 3: void f(int n) { [] { n = 1; }; struct A { void f() { n = 2; } };void g(int = n); [=](int k = n) {}; [&] { [n]{ return n; }; }; } — _end example_]
[Example 4: void g() { constexpr int x = 1;auto lambda = [] <typename T, int = ((T)x, 0)> {}; lambda.operator()<int, 1>(); lambda.operator()<int>(); lambda.operator()<const int&>(); } void h() { constexpr int x = 1;auto lambda = [] <typename T> { (T)x; }; lambda.operator()<int>(); lambda.operator()<void>(); lambda.operator()<const int&>(); } — _end example_]
Every program shall contain at least one definition of every function or variable that is odr-used in that program outside of a discarded statement; no diagnostic required.
[Example 5: auto f() { struct A {};return A{};} decltype(f()) g();auto x = g();
A program containing this translation unit is ill-formed because g is odr-used but not defined, and cannot be defined in any other translation unit because the local class A cannot be named outside this translation unit.
— _end example_]
A definition of an inline function or variable shall be reachable from the end of every definition domain in which it is odr-used outside of a discarded statement.
A definition of a class shall be reachable in every context in which the class is used in a way that requires the class type to be complete.
[Example 6:
The following complete translation unit is well-formed, even though it never defines X:struct X; struct X* x1; X* x2;
— _end example_]
[Note 3:
The rules for declarations and expressions describe in which contexts complete class types are required.
A class type T must be complete if
- an object of type T is defined, or
- a non-static class data member of type T is declared, or
- T is used as the allocated type or array element type in anew-expression ([expr.new]), or
- an lvalue-to-rvalue conversion is applied to a glvalue referring to an object of type T ([conv.lval]), or
- an expression is converted (either implicitly or explicitly) to type T ([conv], [expr.type.conv], [expr.dynamic.cast], [expr.static.cast], [expr.cast]), or
- an expression that is not a null pointer constant, and has type other than cv void*, is converted to the type pointer to Tor reference to T using a standard conversion ([conv]), a dynamic_cast ([expr.dynamic.cast]) or a static_cast ([expr.static.cast]), or
- a class member access operator is applied to an expression of typeT ([expr.ref]), or
- the typeid operator ([expr.typeid]) or thesizeof operator ([expr.sizeof]) is applied to an operand of type T, or
- a function with a return type or argument type of type Tis defined ([basic.def]) or called, or
- a class with a base class of type T is defined ([class.derived]), or
- an lvalue of type T is assigned to ([expr.assign]), or
- the type T is the subject of analignof expression ([expr.alignof]), or
- an exception-declaration has type T, reference toT, or pointer to T ([except.handle]).
— _end note_]
For any definable item D with definitions in multiple translation units,
- if D is a non-inline non-templated function or variable, or
- if the definitions in different translation units do not satisfy the following requirements,
the program is ill-formed; a diagnostic is required only if the definable item is attached to a named module and a prior definition is reachable at the point where a later definition occurs.
Given such an item, for all definitions of D, or, if D is an unnamed enumeration, for all definitions of D that are reachable at any given program point, the following requirements shall be satisfied.
- Each such definition shall not be attached to a named module ([module.unit]).
- Each such definition shall consist of the same sequence of tokens, where the definition of a closure type is considered to consist of the sequence of tokens of the corresponding lambda-expression.
- In each such definition, corresponding names, looked up according to [basic.lookup], shall refer to the same entity, after overload resolution ([over.match]) and after matching of partial template specializations ([temp.spec.partial.match]), except that a name can refer to
- a non-volatile const object with internal or no linkage if the object
* has the same literal type in all definitions of D,
* is initialized with a constant expression,
* is not odr-used in any definition of D, and
* has the same value in all definitions of D,
or
- a reference with internal or no linkage initialized with a constant expression such that the reference refers to the same entity in all definitions of D.
- a non-volatile const object with internal or no linkage if the object
- In each such definition, except within the default arguments and default template arguments of D, corresponding lambda-expressions shall have the same closure type (see below).
- In each such definition, corresponding entities shall have the same language linkage.
- In each such definition, const objects with static or thread storage duration shall be constant-initialized if the object is constant-initialized in any such definition.
- In each such definition, corresponding manifestly constant-evaluated expressions that are not value-dependent shall have the same value ([expr.const], [temp.dep.constexpr]).
- In each such definition, the overloaded operators referred to, the implicit calls to conversion functions, constructors, operator new functions and operator delete functions, shall refer to the same function.
- In each such definition, a default argument used by an (implicit or explicit) function call or a default template argument used by an (implicit or explicit)template-id or simple-template-idis treated as if its token sequence were present in the definition of D; that is, the default argument or default template argument is subject to the requirements described in this paragraph (recursively).
For the purposes of the preceding requirements:
- If D is a class with an implicitly-declared constructor ([class.default.ctor], [class.copy.ctor]), it is as if the constructor was implicitly defined in every translation unit where it is odr-used, and the implicit definition in every translation unit shall call the same constructor for a subobject of D.
[Example 7: struct X { X(int, int); X(int, int, int);}; X::X(int, int = 0) { } class D { X x = 0;}; D d1; struct X { X(int, int); X(int, int, int);}; X::X(int, int = 0, int = 0) { } class D { X x = 0;}; D d2; — _end example_] - If D is a class with a defaulted three-way comparison operator function ([class.spaceship]), it is as if the operator was implicitly defined in every translation unit where it is odr-used, and the implicit definition in every translation unit shall call the same comparison operators for each subobject of D.
- If D is a template and is defined in more than one translation unit, the requirements apply both to names from the template's enclosing scope used in the template definition, and also to dependent names at the point of instantiation ([temp.dep]).
These requirements also apply to corresponding entities defined within each definition of D(including the closure types of lambda-expressions, but excluding entities defined within default arguments or default template arguments of either D or an entity not defined within D).
For each such entity and for D itself, the behavior is as if there is a single entity with a single definition, including in the application of these requirements to other entities.
[Note 4:
The entity is still declared in multiple translation units, and [basic.link]still applies to these declarations.
In particular,lambda-expressions ([expr.prim.lambda]) appearing in the type of D can result in the different declarations having distinct types, andlambda-expressions appearing in a default argument of Dmight still denote different types in different translation units.
— _end note_]
[Example 8: inline void f(bool cond, void (*p)()) { if (cond) f(false, []{});} inline void g(bool cond, void (*p)() = []{}) { if (cond) g(false);} struct X { void h(bool cond, void (*p)() = []{}) { if (cond) h(false);} };
If the definition of f appears in multiple translation units, the behavior of the program is as if there is only one definition of f.
If the definition of g appears in multiple translation units, the program is ill-formed (no diagnostic required) because each such definition uses a default argument that refers to a distinct lambda-expression closure type.
The definition of X can appear in multiple translation units of a valid program; the lambda-expressions defined within the default argument of X::h within the definition of Xdenote the same closure type in each translation unit.
— _end example_]
If, at any point in the program, there is more than one reachable unnamed enumeration definition in the same scope that have the same first enumerator name and do not have typedef names for linkage purposes ([dcl.enum]), those unnamed enumeration types shall be the same; no diagnostic required.
6.4 Scope [basic.scope]
6.4.1 General [basic.scope.scope]
The declarations in a program appear in a number of scopesthat are in general discontiguous.
The global scope contains the entire program; every other scope S is introduced by a declaration,parameter-declaration-clause,statement,handler, or contract assertion (as described in the following subclauses of [basic.scope]) appearing in another scope, which thereby contains S.
An enclosing scope at a program point is any scope that contains it; the smallest such scope is said to be the immediate scopeat that point.
A scope intervenesbetween a program point P and a scope S(that does not contain P) if it is or contains S but does not contain P.
Unless otherwise specified:
- The smallest scope that contains a scope S is the parent scope of S.
- No two declarations (re)introduce the same entity.
- A declaration inhabitsthe immediate scope at its locus ([basic.scope.pdecl]).
- A declaration's target scope is the scope it inhabits.
- Any names (re)introduced by a declaration are bound to it in its target scope.
An entity belongs to a scope Sif S is the target scope of a declaration of the entity.
[Note 1:
Special cases include that:
- Template parameter scopes are parents only to other template parameter scopes ([basic.scope.temp]).
- Corresponding declarations with appropriate linkage declare the same entity ([basic.link]).
- Friend declarations and declarations of template specializations do not bind names ([dcl.meaning]); those with qualified names target a specified scope, and other friend declarations and certain elaborated-type-specifiers ([dcl.type.elab]) target a larger enclosing scope.
- Block-scope extern or function declarations target a larger enclosing scope but bind a name in their immediate scope ([dcl.meaning.general]).
- The names of unscoped enumerators are bound in the two innermost enclosing scopes ([dcl.enum]).
- A class's name is also bound in its own scope ([class.pre]).
- The names of the members of an anonymous union are bound in the union's parent scope ([class.union.anon]).
— _end note_]
Two non-static member functions havecorresponding object parameters if
- exactly one is an implicit object member function with no ref-qualifier and the types of their object parameters ([dcl.fct]), after removing references, are the same, or
- their object parameters have the same type.
Two non-static member function templates havecorresponding object parameters if
- exactly one is an implicit object member function with no ref-qualifier and the types of their object parameters, after removing any references, are equivalent, or
- the types of their object parameters are equivalent.
Two function templates havecorresponding signatures if their template-parameter-list_s_have the same length, their corresponding template-parameters are equivalent, they have equivalent non-object-parameter-type-lists and return types (if any), and, if both are non-static members, they have corresponding object parameters.
Two declarations correspondif they (re)introduce the same name, both declare constructors, or both declare destructors, unless
- either is a using-declarator, or
- one declares a type (not a typedef-name) and the other declares a variable, non-static data member other than of an anonymous union ([class.union.anon]), enumerator, function, or function template, or
- each declares a function or function template and they do not declare corresponding overloads.
Two function or function template declarations declarecorresponding overloads if
- both declare functions with the same non-object-parameter-type-list,17equivalent ([temp.over.link]) trailing requires-clauses(if any, except as specified in [temp.friend]), and, if both are non-static members, they have corresponding object parameters, or
- both declare function templates with corresponding signatures and equivalenttemplate-heads and trailing requires-clauses (if any).
[Note 2:
Declarations can correspond even if neither binds a name.
[Example 1: struct A { friend void f(); };struct B { friend void f() {} }; — _end example_]
— _end note_]
[Example 2: typedef int Int;enum E : int { a };void f(int); void f(Int) {} void f(E) {} struct X { static void f();void f() const; void g();void g() const; void g() &; void h(this X&, int);void h(int) &&; void j(this const X&);void j() const &; void k();void k(this X&); }; — _end example_]
A declaration is name-independentif its name is _ (U+005f low line) and it declares
- a variable with automatic storage duration,
- a structured binding with no storage-class-specifier and not inhabiting a namespace scope,
- a result binding ([dcl.contract.res]),
- the variable introduced by an init-capture, or
- a non-static data member of other than an anonymous union.
Recommended practice: Implementations should not emit a warning that a name-independent declaration is used or unused.
Two declarations potentially conflictif they correspond and cause their shared name to denote different entities ([basic.link]).
The program is ill-formed if, in any scope, a name is bound to two declarations A and Bthat potentially conflict and A precedes B ([basic.lookup]), unless B is name-independent.
[Note 3:
An id-expression that names a unique name-independent declaration is usable until an additional declaration of the same name is introduced in the same scope ([basic.lookup.general]).
— _end note_]
[Note 4:
Overload resolution can consider potentially conflicting declarations found in multiple scopes (e.g., via using-directives or for operator functions), in which case it is often ambiguous.
— _end note_]
[Example 3: void f() { int x,y;void x(); int y; } enum { f }; namespace A {} namespace B = A;namespace B = A; namespace B = B; namespace A = B; namespace B {} void g() { int _; _ = 0; int _; _ = 0; } void h () { int _; _ ++; static int _; } — _end example_]
A declaration is nominablein a class, class template, or namespace E at a point P if it precedes P, it does not inhabit a block scope, and its target scope is the scope associated with E or, if E is a namespace, any element of the inline namespace set of E ([namespace.def]).
[Example 4: namespace A { void f() {void g();} inline namespace B { struct S { friend void h();static int i;};} }
At the end of this example, the declarations of f, B, S, and hare nominable in A, but those of g and i are not.
— _end example_]
When instantiating a templated entity ([temp.pre]), any scope S introduced by any part of the template definition is considered to be introduced by the instantiated entity and to contain the instantiations of any declarations that inhabit S.
6.4.2 Point of declaration [basic.scope.pdecl]
The locus of a declaration ([basic.pre]) that is a declarator is immediately after the complete declarator ([dcl.decl]).
[Example 1: unsigned char x = 12;{ unsigned char x = x; }
Here, the initialization of the second x has undefined behavior, because the initializer accesses the second xoutside its lifetime ([basic.life]).
— _end example_]
[Note 1:
A name from an outer scope remains visible up to the locus of the declaration that hides it.
[Example 2:
const int i = 2;{ int i[i]; } declares a block-scope array of two integers.
— _end example_]
— _end note_]
[Example 3: const int x = 12;{ enum { x = x }; }
Here, the enumerator x is initialized with the value of the constant x, namely 12.
— _end example_]
[Note 2:
After the declaration of a class member, the member name can be found in the scope of its class even if the class is an incomplete class.
[Example 4: struct X { enum E { z = 16 };int b[X::z]; }; — _end example_]
— _end note_]
The locus of an injected-class-name declaration ([class.pre]) is immediately following the opening brace of the class definition.
[Example 5: typedef unsigned char T;template<class T= T , T N = 0> struct A { }; — _end example_]
[Note 5:
Friend declarations can introduce functions or classes that belong to the nearest enclosing namespace or block scope, but they do not bind names anywhere ([class.friend]).
Function declarations at block scope and variable declarations with the extern specifier at block scope declare entities that belong to the nearest enclosing namespace, but they do not bind names in it.
— _end note_]
[Note 6:
For point of instantiation of a template, see [temp.point].
— _end note_]
6.4.3 Block scope [basic.scope.block]
[Note 1:
A substatement that is also a block has only one scope.
— _end note_]
A variable that belongs to a block scope is a block variable.
[Example 1: int i = 42;int a[10];for (int i = 0; i < 10; i++) a[i] = i;int j = i; — _end example_]
[Example 2: if (int x = f()) { int x; } else { int x; } — _end example_]
6.4.4 Function parameter scope [basic.scope.param]
- [Note 2:
In this case, P declares the parameters of a function (or a function or template parameter declared with function type).
A member function's parameter scope is nested within its class's scope.
— _end note_]
6.4.6 Namespace scope [basic.scope.namespace]
For each non-friend redeclaration or specialization whose target scope is or is contained by the scope, the portion after thedeclarator-id,class-head-name, orenum-head-nameis also included in the scope.
The global scope is the namespace scope of the global namespace ([basic.namespace]).
[Example 1: namespace Q { namespace V { void f(); } void V::f() { void h(); } } — _end example_]
6.4.7 Class scope [basic.scope.class]
For each non-friend redeclaration or specialization whose target scope is or is contained by the scope, the portion after thedeclarator-id,class-head-name, orenum-head-name is also included in the scope.
[Note 1:
Lookup from a program point before the class-specifier of a class will find no bindings in the class scope.
[Example 1: template<class D> struct B { D::type x; };struct A { using type = int; };struct C : A, B<C> {}; — _end example_]
— _end note_]
6.4.9 Template parameter scope [basic.scope.temp]
Any declaration outside the template-parameter-listthat would inhabit that scope instead inhabits the same scope as D.
The parent scope of any scope S that is not a template parameter scope is the smallest scope that contains S and is not a template parameter scope.
[Note 1:
Therefore, only template parameters belong to a template parameter scope, and only template parameter scopes have a template parameter scope as a parent scope.
— _end note_]
6.5 Name lookup [basic.lookup]
6.5.1 General [basic.lookup.general]
Name lookup associates the use of a name with a set of declarations ([basic.def]) of that name.
Unless otherwise specified, the program is ill-formed if no declarations are found.
If the declarations found by name lookup all denote functions or function templates, the declarations are said to form an overload set.
Otherwise, if the declarations found by name lookup do not all denote the same entity,they are ambiguous and the program is ill-formed.
The access rules ([class.access]) are considered only once name lookup and function overload resolution (if applicable) have succeeded.
Only after name lookup, function overload resolution (if applicable) and access checking have succeeded are the semantic properties introduced by the declarations used in further processing.
A program point P is said to follow any declaration in the same translation unit whose locus ([basic.scope.pdecl]) is before P.
[Note 1:
The declaration might appear in a scope that does not contain P.
— _end note_]
A declaration X precedesa program point P in a translation unit Lif P follows X, X inhabits a class scope and is reachable from P, or else X appears in a translation unit D and
- P follows a module-import-declaration or module-declarationthat imports D (directly or indirectly), and
- X appears after the module-declaration in D (if any) and before the private-module-fragment in D (if any), and
- either X is exported or else D and L are part of the same module andX does not inhabit a namespace with internal linkage or declare a name with internal linkage.
[Note 3:
[Example 1:
Translation unit #1:export module Q;export int sq(int i) { return i*i; }
Translation unit #2:export module R;export import Q;
Translation unit #3:import R;int main() { return sq(9); } — _end example_]
— _end note_]
A single search in a scope Sfor a name N from a program point Pfinds all declarations that precede Pto which any name that is the same as N ([basic.pre]) is bound in S.
In certain contexts, only certain kinds of declarations are included.
After any such restriction, any declarations of classes or enumerations are discarded if any other declarations are found.
[Note 4:
A type (but not a typedef-name or template) is therefore hidden by any other entity in its scope.
— _end note_]
However, if a lookup is type-only, only declarations of types and templates whose specializations are types are considered; furthermore, if declarations of a typedef-name and of the type to which it refers are found, the declaration of the typedef-name is discarded instead of the type declaration.
6.5.2 Member name lookup [class.member.lookup]
A search in a scope X for a name M from a program point Pis a single search in X for M from Punless X is the scope of a class or class template T, in which case the following steps define the result of the search.
[Note 1:
The result differs only if M is a conversion-function-id or if the single search would find nothing.
— _end note_]
The lookup set for a name N in a class or class template C, called S(N,C), consists of two component sets: the declaration set, a set of members named N; and the subobject set, a set of subobjects where declarations of these members were found (possibly via using-declarations).
In the declaration set, type declarations (including injected-class-names) are replaced by the types they designate.
S(N,C) is calculated as follows:
The declaration set is the result of a single search in the scope of C for Nfrom immediately after the class-specifier of Cif P is in a complete-class context of C or from P otherwise.
If the resulting declaration set is not empty, the subobject set contains C itself, and calculation is complete.
Otherwise (i.e., C does not contain a declaration of Nor the resulting declaration set is empty), S(N,C) is initially empty.
Calculate the lookup set for Nin each direct non-dependent ([temp.dep.type]) base class subobject , and merge each such lookup set in turn into S(N,C).
[Note 2:
If C is incomplete, only base classes whose base-specifier appears before Pare considered.
If C is an instantiated class, its base classes are not dependent.
— _end note_]
The following steps define the result of merging lookup set into the intermediate S(N,C):
- If each of the subobject members of is a base class subobject of at least one of the subobject members of S(N,C), or if is empty, S(N,C) is unchanged and the merge is complete.
Conversely, if each of the subobject members of S(N,C) is a base class subobject of at least one of the subobject members of , or ifS(N,C) is empty, the new S(N,C) is a copy of . - Otherwise, if the declaration sets of and S(N,C)differ, the merge is ambiguous: the new S(N,C) is a lookup set with an invalid declaration set and the union of the subobject sets.
In subsequent merges, an invalid declaration set is considered different from any other. - Otherwise, the new S(N,C) is a lookup set with the shared set of declarations and the union of the subobject sets.
The result of the search is the declaration set of S(M,T).
If it is an invalid set, the program is ill-formed.
If it differs from the result of a search in T for Min a complete-class context ([class.mem]) of T, the program is ill-formed, no diagnostic required.
[Example 1: struct A { int x; }; struct B { float x; }; struct C: public A, public B { }; struct D: public virtual C { }; struct E: public virtual C { char x; }; struct F: public D, public E { }; int main() { F f; f.x = 0; }
S(x,F) is unambiguous because the A and B base class subobjects of D are also base class subobjects of E, soS(x,D) is discarded in the first merge step.
— _end example_]
If M is a non-dependent conversion-function-id, conversion function templates that are members of T are considered.
For each such template F, the lookup set S(t,T) is constructed, considering a function template declaration to have the name tonly if it corresponds to a declaration of F ([basic.scope.scope]).
The members of the declaration set of each such lookup set, which shall not be an invalid set, are included in the result.
[Note 3:
Overload resolution will discard those that cannot convert to the type specified by M ([temp.over]).
— _end note_]
[Note 4:
A static member, a nested type or an enumerator defined in a base classT can unambiguously be found even if an object has more than one base class subobject of type T.
Two base class subobjects share the non-static member subobjects of their common virtual base classes.
— _end note_]
[Example 2: struct V { int v;};struct A { int a;static int s;enum { e };};struct B : A, virtual V { };struct C : A, virtual V { };struct D : B, C { };void f(D* pd) { pd->v++; pd->s++; int i = pd->e; pd->a++; } — _end example_]
[Note 5:
When virtual base classes are used, a hidden declaration can be reached along a path through the subobject lattice that does not pass through the hiding declaration.
This is not an ambiguity.
The identical use with non-virtual base classes is an ambiguity; in that case there is no unique instance of the name that hides all the others.
— _end note_]
[Example 3: struct V { int f(); int x; };struct W { int g(); int y; };struct B : virtual V, W { int f(); int x;int g(); int y;};struct C : virtual V, W { };struct D : B, C { void glorp(); };
virt W1 W V V W2 W B B B->W1 B->V C C C->V C->W2 D D D->B D->C
Figure 1 — Name lookup [fig:class.lookup]
As illustrated in Figure 1, the names declared in V and the left-hand instance of Ware hidden by those in B, but the names declared in the right-hand instance of W are not hidden at all.
void D::glorp() { x++; f(); y++; g(); } — _end example_]
An explicit or implicit conversion from a pointer to or an expression designating an object of a derived class to a pointer or reference to one of its base classes shall unambiguously refer to a unique object representing the base class.
[Example 4: struct V { };struct A { };struct B : A, virtual V { };struct C : A, virtual V { };struct D : B, C { };void g() { D d; B* pb = &d; A* pa = &d; V* pv = &d; } — _end example_]
[Example 5: struct B1 { void f();static void f(int);int i;};struct B2 { void f(double);};struct I1: B1 { };struct I2: B1 { };struct D: I1, I2, B2 { using B1::f;using B2::f;void g() { f(); f(0); f(0.0); int B1::* mpB1 = &D::i; int D::* mpD = &D::i; } }; — _end example_]
6.5.3 Unqualified name lookup [basic.lookup.unqual]
A using-directive isactive in a scope S at a program point Pif it precedes P and inhabits either S or the scope of a namespace nominated by a using-directivethat is active in S at P.
An unqualified search in a scope S from a program point Pincludes the results of searches from P in
- S, and
- for any scope U that contains P and is or is contained by S, each namespace contained by S that is nominated by a using-directive that is active in U at P.
If no declarations are found, the results of the unqualified search are the results of an unqualified search in the parent scope of S, if any, from P.
[Note 1:
When a class scope is searched, the scopes of its base classes are also searched ([class.member.lookup]).
If it inherits from a single base, it is as if the scope of the base immediately contains the scope of the derived class.
Template parameter scopes that are associated with one scope in the chain of parents are also considered ([temp.local]).
— _end note_]
Unqualified name lookupfrom a program point performs an unqualified search in its immediate scope.
An unqualified name is a name that does not immediately follow a nested-name-specifier or the . or -> in a class member access expression ([expr.ref]), possibly after a template keyword or ~.
Unless otherwise specified, such a name undergoes unqualified name lookup from the point where it appears.
If that lookup finds nothing, it undergoes unqualified name lookup; in each case, only names that denote types or templates whose specializations are types are considered.
[Example 1: struct T1 { struct U { int i; }; };struct T2 { };struct U1 {};struct U2 {};struct B { using T = T1;using U = U1;operator U1 T1::*();operator U1 T2::*();operator U2 T1::*();operator U2 T2::*();};template<class X, class T> int g() { using U = U2; X().operator U T::*(); X().operator U decltype(T())::*(); return 0;} int x = g<B, T2>(); — _end example_]
In a friend declaration declaratorwhose declarator-id is a qualified-idwhose lookup context ([basic.lookup.qual]) is a class or namespace S, lookup for an unqualified name that appears after the declarator-idperforms a search in the scope associated with S.
If that lookup finds nothing, it undergoes unqualified name lookup.
[Example 2: using I = int;using D = double;namespace A { inline namespace N {using C = char; } using F = float;void f(I);void f(D);void f(C);void f(F);} struct X0 {using F = float; };struct W { using D = void;struct X : X0 { void g(I);void g(::D);void g(F);};};namespace B { typedef short I, F;class Y { friend void A::f(I); friend void A::f(D); friend void A::f(C); friend void A::f(F); friend void W::X::g(I); friend void W::X::g(D); friend void W::X::g(F); };} — _end example_]
6.5.4 Argument-dependent name lookup [basic.lookup.argdep]
When the postfix-expression in a function call ([expr.call]) is an unqualified-id, and unqualified lookup ([basic.lookup.unqual]) for the name in the unqualified-id does not find any
- declaration of a class member, or
- function declaration inhabiting a block scope, or
- declaration not of a function or function template
then lookup for the name also includes the result ofargument-dependent lookup in a set of associated namespaces that depends on the types of the arguments (and for type template template arguments, the namespace of the template argument), as specified below.
[Example 1: namespace N { struct S { };void f(S);} void g() { N::S s; f(s); (f)(s); } — _end example_]
[Note 1:
For purposes of determining (during parsing) whether an expression is apostfix-expression for a function call, the usual name lookup rules apply.
For example,int h;void g();namespace N { struct A {};template <class T> int f(T);template <class T> int g(T);template <class T> int h(T);} int x = f<N::A>(N::A()); int y = g<N::A>(N::A()); int z = h<N::A>(N::A());
The rules have no effect on the syntactic interpretation of an expression.
For example,typedef int f;namespace N { struct A { friend void f(A &);operator int();void g(A a) { int i = f(a); } };}
Because the expression is not a function call, argument-dependent name lookup does not apply and the friend function f is not found.
— _end note_]
For each argument type T in the function call, there is a set of zero or more associated entitiesto be considered.
The set of entities is determined entirely by the types of the function arguments (and any type template template arguments).
The set of entities is determined in the following way:
- If T is a fundamental type, its associated set of entities is empty.
- If T is a class type (including unions), its associated entities are: the class itself; the class of which it is a member, if any; and, if it is a complete type, its direct and indirect base classes.
Furthermore, if T is a class template specialization, its associated entities also include: the entities associated with the types of the template arguments provided for template type parameters; the templates used as type template template arguments; and the classes of which any member templates used as type template template arguments are members.
[Note 2:
Constant template arguments, variable template template arguments, and concept template arguments do not contribute to the set of associated entities.
— _end note_] - If T is an enumeration type, its associated entities are Tand, if it is a class member, the member's class.
- If T is a pointer to U or an array of U, its associated entities are those associated with U.
- If T is a function type, its associated entities are those associated with the function parameter types and those associated with the return type.
- If T is a pointer to a member function of a classX, its associated entities are those associated with the function parameter types and return type, together with those associated with X.
- If T is a pointer to a data member of class X, its associated entities are those associated with the member type together with those associated with X.
In addition, if the argument is an overload set or the address of such a set, its associated entities are the union of those associated with each of the members of the set, i.e., the entities associated with its parameter types and return type.
Additionally, if the aforementioned overload set is named with a template-id, its associated entities also include its template template arguments and those associated with its type template arguments.
The associated namespaces for a call are the innermost enclosing non-inline namespaces for its associated entities as well as every element of the inline namespace set ([namespace.def]) of those namespaces.
Argument-dependent lookup finds all declarations of functions and function templates that
- are found by a search of any associated namespace, or
- are declared as a friend ([class.friend]) of any class with a reachable definition in the set of associated entities, or
- are exported, are attached to a named module M ([module.interface]), do not appear in the translation unit containing the point of the lookup, and have the same innermost enclosing non-inline namespace scope as a declaration of an associated entity attached to M ([basic.link]).
If the lookup is for a dependent name ([temp.dep], [temp.dep.candidate]), the above lookup is also performed from each point in the instantiation context ([module.context]) of the lookup, additionally ignoring any declaration that appears in another translation unit, is attached to the global module, and is either discarded ([module.global.frag]) or has internal linkage.
[Example 2:
Translation unit #1:export module M;namespace R { export struct X {};export void f(X);} namespace S { export void f(R::X, R::X);}
Translation unit #2:export module N;import M;export R::X make();namespace R { static int g(X); } export template<typename T, typename U> void apply(T t, U u) { f(t, u); g(t);}
Translation unit #3:module Q;import N;namespace S { struct Z { template<typename T> operator T(); };} void test() { auto x = make(); R::f(x); f(x); f(x, S::Z()); apply(x, S::Z()); } — _end example_]
[Note 3:
The associated namespace can include namespaces already considered by ordinary unqualified lookup.
— _end note_]
[Example 3: namespace NS { class T { };void f(T);void g(T, int);}NS::T parm;void g(NS::T, float);int main() { f(parm); extern void g(NS::T, float); g(parm, 1); } — _end example_]
6.5.5 Qualified name lookup [basic.lookup.qual]
6.5.5.1 General [basic.lookup.qual.general]
Lookup of an identifierfollowed by a :: scope resolution operator considers only namespaces, types, and templates whose specializations are types.
[Example 1: class A { public: static int n;};int main() { int A; A::n = 42; A b; } template<int> struct B : A {};namespace N { template<int> void B();int f() { return B<0>::n; } } — _end example_]
A qualified name is
- a member-qualified name or
- the terminal name of
The lookup context of a member-qualified name is the type of its associated object expression (considered dependent if the object expression is type-dependent).
The lookup context of any other qualified name is the type, template, or namespace nominated by the preceding nested-name-specifier.
[Note 1:
When parsing a class member access, the name following the -> or . is a qualified name even though it is not yet known of which kind.
— _end note_]
[Example 2:
In N::C::m.Base::f() Base is a member-qualified name; the other qualified names are C, m, and f.
— _end example_]
Qualified name lookupin a class, namespace, or enumeration performs a search of the scope associated with it ([class.member.lookup]) except as specified below.
Unless otherwise specified, a qualified name undergoes qualified name lookup in its lookup context from the point where it appears unless the lookup context either is dependent and is not the current instantiation ([temp.dep.type]) or is not a class or class template.
If nothing is found by qualified lookup for a member-qualified name that is the terminal name ([expr.prim.id.unqual]) of a nested-name-specifier and is not dependent, it undergoes unqualified lookup.
[Note 2:
During lookup for a template specialization, no names are dependent.
— _end note_]
[Example 3: int f();struct A { int B, C;template<int> using D = void;using T = void;void f();};using B = A;template<int> using C = A;template<int> using D = A;template<int> using X = A;template<class T> void g(T *p) { p->X<0>::f(); p->template X<0>::f(); p->B::f(); p->template C<0>::f(); p->template D<0>::f(); p->T::f(); } template void g(A*); — _end example_]
If a qualified name Q follows a ~:
- If Q is a member-qualified name, it undergoes unqualified lookup as well as qualified lookup.
- If N has another nested-name-specifier S,Q is looked up as if its lookup context were that nominated by S.
- Otherwise, if the terminal name of N is a member-qualified name M,Q is looked up as if ~Q appeared in place of M (as above).
- Otherwise, Q undergoes unqualified lookup.
- Each lookup for Q considers only types (if Q is not followed by a <) and templates whose specializations are types.
If it finds nothing or is ambiguous, it is discarded. - The type-name that is or contains Qshall refer to its (original) lookup context (ignoring cv-qualification) under the interpretation established by at least one (successful) lookup performed.
[Example 4: struct C { typedef int I;};typedef int I1, I2;extern int* p;extern int* q;void f() { p->C::I::~I(); q->I1::~I2(); } struct A { ~A();};typedef A AB;int main() { AB* p; p->AB::~AB(); } — _end example_]
6.5.5.2 Class members [class.qual]
In a lookup for a qualified name N whose lookup context is a class Cin which function names are not ignored,18
- if the search finds the injected-class-name of C ([class.pre]), or
- if N is dependent and is the terminal name of a using-declarator ([namespace.udecl]) that names a constructor,
N is instead considered to name the constructor of class C.
Such a constructor name shall be used only in the declarator-id of a (friend) declaration of a constructor or in a using-declaration.
[Example 1: struct A { A(); };struct B: public A { B(); }; A::A() { }B::B() { }B::A ba; A::A a; struct A::A a2; — _end example_]
6.5.5.3 Namespace members [namespace.qual]
Qualified name lookup in a namespace N additionally searches every element of the inline namespace set of N ([namespace.def]).
If nothing is found, the results of the lookup are the results of qualified name lookup in each namespace nominated by a using-directivethat precedes the point of the lookup and inhabits N or an element of N's inline namespace set.
[Note 1:
If a using-directive refers to a namespace that has already been considered, it does not affect the result.
— _end note_]
[Example 1: int x;namespace Y { void f(float);void h(int);} namespace Z { void h(double);} namespace A { using namespace Y;void f(int);void g(int);int i;} namespace B { using namespace Z;void f(char);int i;} namespace AB { using namespace A;using namespace B;void g();} void h() { AB::g(); AB::f(1); AB::f('c'); AB::x++; AB::i++; AB::h(16.8); } — _end example_]
[Note 2:
The same declaration found more than once is not an ambiguity (because it is still a unique declaration).
[Example 2: namespace A { int a;} namespace B { using namespace A;} namespace C { using namespace A;} namespace BC { using namespace B;using namespace C;} void f() { BC::a++; } namespace D { using A::a;} namespace BD { using namespace B;using namespace D;} void g() { BD::a++; } — _end example_]
— _end note_]
[Example 3:
Because each referenced namespace is searched at most once, the following is well-defined:namespace B { int b;} namespace A { using namespace B;int a;} namespace B { using namespace A;} void f() { A::a++; B::a++; A::b++; B::b++; }
— _end example_]
[Note 3:
Class and enumeration declarations are not discarded because of other declarations found in other searches.
— _end note_]
[Example 4: namespace A { struct x { };int x;int y;} namespace B { struct y { };} namespace C { using namespace A;using namespace B;int i = C::x; int j = C::y; } — _end example_]
6.5.6 Elaborated type specifiers [basic.lookup.elab]
[Example 1: struct Node { struct Node* Next; struct Data* Data; };struct Data { struct Node* Node; friend struct ::Glob; friend struct Glob; };struct Base { struct Data; struct ::Data* thatData; struct Base::Data* thisData; friend class ::Data; friend class Data; struct Data { }; };struct Data; struct ::Data; struct Base::Data; struct Base::Datum; struct Base::Data* pBase; — _end example_]
6.6 Program and linkage [basic.link]
A translation unit consists of a sequence of declarations.
A name can haveexternal linkage,module linkage,internal linkage, orno linkage, as determined by the rules below.
[Note 1:
All declarations of an entity with a name with internal linkage appear in the same translation unit.
All declarations of an entity with module linkage are attached to the same module.
— _end note_]
The name of an entity that belongs to a namespace scopehas internal linkage if it is the name of
- a variable, variable template, function, or function template that is explicitly declared static; or
- a non-template variable of non-volatile const-qualified type, unless
- it is declared in the purview of a module interface unit (outside the private-module-fragment, if any) or module partition, or
- it is explicitly declared extern, or
- it is inline, or
- it was previously declared and the prior declaration did not have internal linkage; or
- a data member of an anonymous union.
[Note 2:
An instantiated variable template that has const-qualified type can have external or module linkage, even if not declared extern.
— _end note_]
An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage.
All other namespaces have external linkage.
The name of an entity that belongs to a namespace scope, that has not been given internal linkage above, and that is the name of
- a variable; or
- a function; or
- a named class ([class.pre]), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes ([dcl.typedef]); or
- a named enumeration, or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes ([dcl.typedef]); or
- an unnamed enumeration that has an enumerator as a name for linkage purposes ([dcl.enum]); or
- a template
has its linkage determined as follows:
- if the entity is a function or function template first declared in a friend declaration and that declaration is a definition and the enclosing class is defined within an export-declaration, the name has the same linkage, if any, as the name of the enclosing class ([class.friend]);
- otherwise,if the entity is a function or function template declared in a friend declaration and a corresponding non-friend declaration is reachable, the name has the linkage determined from that prior declaration,
- otherwise, if the enclosing namespace has internal linkage, the name has internal linkage;
- otherwise, if the declaration of the name is attached to a named module ([module.unit]) and is not exported ([module.interface]), the name has module linkage;
- otherwise, the name has external linkage.
In addition, a member function, a static data member, a named class or enumeration that inhabits a class scope, or an unnamed class or enumeration defined in a typedef declaration that inhabits a class scope such that the class or enumeration has the typedef name for linkage purposes ([dcl.typedef]), has the same linkage, if any, as the name of the class of which it is a member.
[Example 1: static void f();extern "C" void h();static int i = 0; void q() { extern void f(); extern void g(); extern void h(); int i; { extern void f(); extern int i; } }
Even though the declaration at line #2 hides the declaration at line #1, the declaration at line #3 still redeclares #1 and receives internal linkage.
— _end example_]
Names not covered by these rules have no linkage.
Moreover, except as noted, a name declared at block scope has no linkage.
Two declarations of entities declare the same entity if, considering declarations of unnamed types to introduce their names for linkage purposes, if any ([dcl.typedef], [dcl.enum]), they correspond ([basic.scope.scope]), have the same target scope that is not a function or template parameter scope, neither is a name-independent declaration, and either
- they appear in the same translation unit, or
- they both declare names with module linkage and are attached to the same module, or
- they both declare names with external linkage.
If a declaration H that declares a name with internal linkage precedes a declaration D in another translation unit U and would declare the same entity as D if it appeared in U, the program is ill-formed.
[Note 4:
Such an H can appear only in a header unit.
— _end note_]
If two declarations of an entity are attached to different modules, the program is ill-formed; no diagnostic is required if neither is reachable from the other.
[Example 2:
"decls.h":int f(); int g();
Module interface of M:module;#include "decls.h" export module M;export using ::f; int g(); export int h(); export int k();
Other translation unit:import M;static int h(); int k(); — _end example_]
As a consequence of these rules, all declarations of an entity are attached to the same module; the entity is said to be attached to that module.
For any two declarations of an entity E:
- If one declares E to be a variable or function, the other shall declare E as one of the same type.
- If one declares E to be an enumerator, the other shall do so.
- If one declares E to be a namespace, the other shall do so.
- If one declares E to be a type, the other shall declare E to be a type of the same kind ([dcl.type.elab]).
- If one declares E to be a class template, the other shall do so with the same kind and an equivalent template-head ([temp.over.link]).
[Note 5:
The declarations can supply different default template arguments.
— _end note_] - If one declares E to be a function template or a (partial specialization of a) variable template, the other shall declare E to be one with an equivalent template-head and type.
- If one declares E to be a concept, the other shall do so.
Types are compared after all adjustments of types (during which typedefs ([dcl.typedef]) are replaced by their definitions); declarations for an array object can specify array types that differ by the presence or absence of a major array bound ([dcl.array]).
No diagnostic is required if neither declaration is reachable from the other.
[Example 3: int f(int x, int x); void g(); void g(int); int g(); void h(); namespace h {} — _end example_]
A declaration D names an entity E if
- D contains a lambda-expression whose closure type is E,
- E is not a function or function template and D contains anid-expression,type-specifier,nested-name-specifier,template-name, orconcept-namedenoting E, or
- E is a function or function template andD contains an expression that names E ([basic.def.odr]) or an id-expressionthat refers to a set of overloads that contains E.
[Note 7:
Non-dependent names in an instantiated declaration do not refer to a set of overloads ([temp.res]).
— _end note_]
A declaration is an exposureif it either names a TU-local entity (defined below), ignoring
- the function-bodyfor a non-inline function or function template (but not the deduced return type for a (possibly instantiated) definition of a function with a declared return type that uses a placeholder type ([dcl.spec.auto])),
- the initializerfor a variable or variable template (but not the variable's type),
- friend declarations in a class definition, and
- any reference to a non-volatile const object or reference with internal or no linkage initialized with a constant expression that is not an odr-use ([basic.def.odr]),
or defines a constexpr variable initialized to a TU-local value (defined below).
[Note 8:
An inline function template can be an exposure even though certain explicit specializations of it would be usable in other translation units.
— _end note_]
An entity is TU-local if it is
- a type, function, variable, or template that
- has a name with internal linkage, or
- does not have a name with linkage and is declared, or introduced by a lambda-expression, within the definition of a TU-local entity,
- a type with no name that is defined outside aclass-specifier, function body, orinitializeror is introduced by a defining-type-specifierthat is used to declare only TU-local entities,
- a specialization of a TU-local template,
- a specialization of a template with any TU-local template argument, or
- a specialization of a template whose (possibly instantiated) declaration is an exposure.
[Note 9:
A specialization can be produced by implicit or explicit instantiation.
— _end note_]
A value or object is TU-local if either
- it is, or is a pointer to, a TU-local function or the object associated with a TU-local variable, or
- it is an object of class or array type and any of its subobjects or any of the objects or functions to which its non-static data members of reference type refer is TU-local and is usable in constant expressions.
If a (possibly instantiated) declaration of, or a deduction guide for, a non-TU-local entity in a module interface unit (outside the private-module-fragment, if any) or module partition ([module.unit]) is an exposure, the program is ill-formed.
Such a declaration in any other context is deprecated ([depr.local]).
If a declaration that appears in one translation unit names a TU-local entity declared in another translation unit that is not a header unit, the program is ill-formed.
A declaration instantiated for a template specialization ([temp.spec]) appears at the point of instantiation of the specialization ([temp.point]).
[Example 4:
Translation unit #1:export module A;static void f() {} inline void it() { f(); } static inline void its() { f(); } template<int> void g() { its(); } template void g<0>();decltype(f) *fp; auto &fr = f; constexpr auto &fr2 = fr; constexpr static auto fp2 = fr; struct S { void (&ref)(); } s{f}; constexpr extern struct W { S &s; } wrap{s}; static auto x = []{f();}; auto x2 = x; int y = ([]{f();}(),0); int y2 = (x,0); namespace N { struct A {};void adl(A);static void adl(int);} void adl(double);inline void h(auto x) { adl(x); }
Translation unit #2:module A;void other() { g<0>(); g<1>(); h(N::A{}); h(0); adl(N::A{}); fr(); constexpr auto ptr = fr; } — _end example_]
6.7 Memory and objects [basic.memobj]
6.7.1 Memory model [intro.memory]
The fundamental storage unit in the C++ memory model is thebyte.
A byte is at least large enough to contain the ordinary literal encoding of any element of the basicliteral character set ([lex.charset]) and the eight-bit code units of the UnicodeUTF-8 encoding form and is composed of a contiguous sequence of bits,19the number of which is implementation-defined.
The memory available to a C++ program consists of one or more sequences of contiguous bytes.
Every byte has a unique address.
[Note 1:
The representation of types is described in [basic.types.general].
— _end note_]
A memory location is the storage occupied by the object representation of either an object of scalar type that is not a bit-field or a maximal sequence of adjacent bit-fields all having nonzero width.
[Note 2:
Various features of the language, such as references and virtual functions, might involve additional memory locations that are not accessible to programs but are managed by the implementation.
— _end note_]
Two or more threads of execution can access separate memory locations without interfering with each other.
[Note 3:
Thus a bit-field and an adjacent non-bit-field are in separate memory locations, and therefore can be concurrently updated by two threads of execution without interference.
The same applies to two bit-fields, if one is declared inside a nested struct declaration and the other is not, or if the two are separated by a zero-length bit-field declaration, or if they are separated by a non-bit-field declaration.
It is not safe to concurrently update two bit-fields in the same struct if all fields between them are also bit-fields of nonzero width.
— _end note_]
[Example 1:
A class declared asstruct { char a;int b:5, c:11,:0, d:8;struct {int ee:8;} e;};contains four separate memory locations: The member a and bit-fieldsd and e.ee are each separate memory locations, and can be modified concurrently without interfering with each other.
The bit-fieldsb and c together constitute the fourth memory location.
The bit-fields b and c cannot be concurrently modified, butb and a, for example, can be.
— _end example_]
6.7.2 Object model [intro.object]
The constructs in a C++ program create, destroy, refer to, access, and manipulate objects.
An object is created by a definition, by a new-expression ([expr.new]), by an operation that implicitly creates objects (see below), when implicitly changing the active member of a union, or when a temporary object is created ([conv.rval], [class.temporary]).
An object occupies a region of storage in its period of construction ([class.cdtor]), throughout its lifetime, and in its period of destruction ([class.cdtor]).
[Note 1:
A function is not an object, regardless of whether or not it occupies storage in the way that objects do.
— _end note_]
The properties of an object are determined when the object is created.
An object can have a name ([basic.pre]).
An object has a storage duration ([basic.stc]) which influences its lifetime ([basic.life]).
An object has a type ([basic.types]).
[Note 2:
Some objects are polymorphic ([class.virtual]); the implementation generates information associated with each such object that makes it possible to determine that object's type during program execution.
— _end note_]
Objects can contain other objects, called subobjects.
An object that is not a subobject of any other object is called a complete object.
If an object is created in storage associated with a member subobject or array element e(which may or may not be within its lifetime), the created object is a subobject of e's containing object if
- the lifetime of e's containing object has begun and not ended, and
- the storage for the new object exactly overlays the storage location associated with e, and
- the new object is of the same type as e (ignoring cv-qualification).
If a complete object is created ([expr.new]) in storage associated with another object _e_of type “array of N unsigned char” or of type “array of N std::byte” ([cstddef.syn]), that array provides storagefor the created object if
- the lifetime of e has begun and not ended, and
- the storage for the new object fits entirely within e, and
- there is no array object that satisfies these constraints nested within e.
[Note 3:
If that portion of the array previously provided storage for another object, the lifetime of that object ends because its storage was reused ([basic.life]).
— _end note_]
[Example 1: template<typename ...T> struct AlignedUnion { alignas(T...) unsigned char data[max(sizeof(T)...)];};int f() { AlignedUnion<int, char> au;int *p = new (au.data) int; char *c = new (au.data) char(); char *d = new (au.data + 1) char();return *c + *d; } struct A { unsigned char a[32]; };struct B { unsigned char b[16]; };alignas(int) A a; B *b = new (a.a + 8) B; int *p = new (b->b + 4) int; — _end example_]
An object a is nested within another object b if
- a is a subobject of b, or
- b provides storage for a, or
- there exists an object _c_where a is nested within c, and c is nested within b.
For every object x, there is some object called thecomplete object of x, determined as follows:
- If x is a complete object, then the complete object of x is itself.
- Otherwise, the complete object of x is the complete object of the (unique) object that contains x.
If a complete object, a member subobject, or an array element is of class type, its type is considered the most derived class, to distinguish it from the class type of any base class subobject; an object of a most derived class type or of a non-class type is called amost derived object.
A potentially-overlapping subobject is either:
- a base class subobject, or
- a non-static data member declared with the no_unique_address attribute.
An object has nonzero size if it
- is not a potentially-overlapping subobject, or
- is not of class type, or
- is of a class type with virtual member functions or virtual base classes, or
- has subobjects of nonzero size or unnamed bit-fields of nonzero length.
Otherwise, if the object is a base class subobject of a standard-layout class type with no non-static data members, it has zero size.
Otherwise, the circumstances under which the object has zero size are implementation-defined.
Unless it is a bit-field, an object with nonzero size shall occupy one or more bytes of storage, including every byte that is occupied in full or in part by any of its subobjects.
An object of trivially copyable or standard-layout type ([basic.types.general]) shall occupy contiguous bytes of storage.
An object is a potentially non-unique object if it is a string literal object ([lex.string]), the backing array of an initializer list ([dcl.init.ref]), or a subobject thereof.
Unless an object is a bit-field or a subobject of zero size, the address of that object is the address of the first byte it occupies.
Two objects with overlapping lifetimes that are not bit-fields may have the same address if
- one is nested within the other,
- at least one is a subobject of zero size and they are not of similar types ([conv.qual]), or
- they are both potentially non-unique objects;
otherwise, they have distinct addresses and occupy disjoint bytes of storage.20
[Example 2: static const char test1 = 'x';static const char test2 = 'x';const bool b = &test1 != &test2; static const char (&r) [] = "x";static const char *s = "x";static std::initializer_list<char> il = { 'x' };const bool b2 = r != il.begin(); const bool b3 = r != s; const bool b4 = il.begin() != &test1; const bool b5 = r != &test1; — _end example_]
The address of a non-bit-field subobject of zero size is the address of an unspecified byte of storage occupied by the complete object of that subobject.
Some operations are described asimplicitly creating objectswithin a specified region of storage.
For each operation that is specified as implicitly creating objects, that operation implicitly creates and starts the lifetime of zero or more objects of implicit-lifetime types ([basic.types.general]) in its specified region of storage if doing so would result in the program having defined behavior.
If no such set of objects would give the program defined behavior, the behavior of the program is undefined.
If multiple such sets of objects would give the program defined behavior, it is unspecified which such set of objects is created.
[Note 4:
Such operations do not start the lifetimes of subobjects of such objects that are not themselves of implicit-lifetime types.
— _end note_]
Further, after implicitly creating objects within a specified region of storage, some operations are described as producing a pointer to asuitable created object.
These operations select one of the implicitly-created objects whose address is the address of the start of the region of storage, and produce a pointer value that points to that object, if that value would result in the program having defined behavior.
If no such pointer value would give the program defined behavior, the behavior of the program is undefined.
If multiple such pointer values would give the program defined behavior, it is unspecified which such pointer value is produced.
[Example 3: #include <cstdlib> struct X { int a, b; }; X *make_x() { X *p = (X*)std::malloc(sizeof(struct X)); p->a = 1; p->b = 2;return p;} — _end example_]
Except during constant evaluation, an operation that begins the lifetime of an array of unsigned char or std::byteimplicitly creates objects within the region of storage occupied by the array.
[Note 5:
The array object provides storage for these objects.
— _end note_]
Except during constant evaluation, any implicit or explicit invocation of a function named operator new or operator new[]implicitly creates objects in the returned region of storage and returns a pointer to a suitable created object.
6.7.3 Alignment [basic.align]
Object types have alignment requirements ([basic.fundamental], [basic.compound]) which place restrictions on the addresses at which an object of that type may be allocated.
An alignment is an implementation-defined integer value representing the number of bytes between successive addresses at which a given object can be allocated.
An object type imposes an alignment requirement on every object of that type; stricter alignment can be requested using the alignment specifier ([dcl.align]).
Attempting to create an object ([intro.object]) in storage that does not meet the alignment requirements of the object's type is undefined behavior.
A fundamental alignment is represented by an alignment less than or equal to the greatest alignment supported by the implementation in all contexts, which is equal toalignof(std::max_align_t) ([support.types]).
The alignment required for a type may be different when it is used as the type of a complete object and when it is used as the type of a subobject.
[Example 1: struct B { long double d; };struct D : virtual B { char c; };
When D is the type of a complete object, it will have a subobject of type B, so it must be aligned appropriately for a long double.
If D appears as a subobject of another object that also has Bas a virtual base class, the B subobject might be part of a different subobject, reducing the alignment requirements on the D subobject.
— _end example_]
The result of the alignof operator reflects the alignment requirement of the type in the complete-object case.
An extended alignment is represented by an alignment greater than alignof(std::max_align_t).
It is implementation-defined whether any extended alignments are supported and the contexts in which they are supported ([dcl.align]).
A type having an extended alignment requirement is an over-aligned type.
[Note 1:
Every over-aligned type is or contains a class type to which extended alignment applies (possibly through a non-static data member).
— _end note_]
A new-extended alignment is represented by an alignment greater than __STDCPP_DEFAULT_NEW_ALIGNMENT__ ([cpp.predefined]).
Alignments are represented as values of the type std::size_t.
Valid alignments include only those values returned by an alignofexpression for the fundamental types plus an additional implementation-defined set of values, which may be empty.
Every alignment value shall be a non-negative integral power of two.
Alignments have an order from weaker tostronger or stricter alignments.
Stricter alignments have larger alignment values.
An address that satisfies an alignment requirement also satisfies any weaker valid alignment requirement.
The alignment requirement of a complete type can be queried using analignof expression ([expr.alignof]).
Furthermore, the narrow character types ([basic.fundamental]) shall have the weakest alignment requirement.
[Note 2:
This enables the ordinary character types to be used as the underlying type for an aligned memory area ([dcl.align]).
— _end note_]
Comparing alignments is meaningful and provides the obvious results:
- Two alignments are equal when their numeric values are equal.
- Two alignments are different when their numeric values are not equal.
- When an alignment is larger than another it represents a stricter alignment.
[Note 3:
The runtime pointer alignment function ([ptr.align]) can be used to obtain an aligned pointer within a buffer; an alignment-specifier ([dcl.align]) can be used to align storage explicitly.
— _end note_]
If a request for a specific extended alignment in a specific context is not supported by an implementation, the program is ill-formed.
6.7.4 Lifetime [basic.life]
In this subclause, “before” and “after” refer to the “happens before” relation ([intro.multithread]).
The lifetime of an object or reference is a runtime property of the object or reference.
A variable is said to have vacuous initializationif it is default-initialized, no other initialization is performed, and, if it is of class type or a (possibly multidimensional) array thereof, a trivial constructor of that class type is selected for the default-initialization.
The lifetime of an object o of type T ends when:
- if T is a non-class type, the object is destroyed, or
- if T is a class type, the destructor call starts, or
- the storage which the object occupies is released, or is reused by an object that is not nested within o ([intro.object]).
When evaluating a new-expression, storage is considered reused after it is returned from the allocation function, but before the evaluation of the new-initializer ([expr.new]).
[Example 1: struct S { int m;};void f() { S x{1};new(&x) S(x.m); } — _end example_]
The lifetime of a reference begins when its initialization is complete.
The lifetime of a reference ends as if it were a scalar object requiring storage.
[Note 1:
[class.base.init]describes the lifetime of base and member subobjects.
— _end note_]
The properties ascribed to objects and references throughout this document apply for a given object or reference only during its lifetime.
[Note 2:
In particular, before the lifetime of an object starts and after its lifetime ends there are significant restrictions on the use of the object, as described below, in [class.base.init], and in [class.cdtor].
Also, the behavior of an object under construction and destruction can differ from the behavior of an object whose lifetime has started and not ended.
[class.base.init]and [class.cdtor] describe the behavior of an object during its periods of construction and destruction.
— _end note_]
A program may end the lifetime of an object of class type without invoking the destructor, by reusing or releasing the storage as described above.
In this case, the destructor is not implicitly invoked.
[Note 4:
The correct behavior of a program often depends on the destructor being invoked for each object of class type.
— _end note_]
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated21or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways.
For an object under construction or destruction, see [class.cdtor].
Otherwise, such a pointer refers to allocated storage ([basic.stc.dynamic.allocation]), and using the pointer as if the pointer were of type void* is well-defined.
Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below.
The program has undefined behavior if
- the pointer is used as the operand of a delete-expression,
- the pointer is used to access a non-static data member or call a non-static member function of the object, or
- the pointer is implicitly converted ([conv.ptr]) to a pointer to a virtual base class, or
- the pointer is used as the operand of astatic_cast ([expr.static.cast]), except when the conversion is to pointer to cv void, or to pointer to cv void and subsequently to pointer tocv char,cv unsigned char, orcv std::byte ([cstddef.syn]), or
- the pointer is used as the operand of adynamic_cast ([expr.dynamic.cast]).
[Example 2: #include <cstdlib> struct B { virtual void f();void mutate();virtual ~B();};struct D1 : B { void f(); };struct D2 : B { void f(); };void B::mutate() { new (this) D2; f(); ... = this; } void g() { void* p = std::malloc(sizeof(D1) + sizeof(D2)); B* pb = new (p) D1; pb->mutate();*pb; void* q = pb; pb->f(); } — _end example_]
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 glvalue that refers to the original object may be used but only in limited ways.
For an object under construction or destruction, see [class.cdtor].
Otherwise, such a glvalue refers to allocated storage ([basic.stc.dynamic.allocation]), and using the properties of the glvalue that do not depend on its value is well-defined.
The program has undefined behavior if
- the glvalue is used to access the object, or
- the glvalue is used to call a non-static member function of the object, or
- the glvalue is bound to a reference to a virtual base class ([dcl.init.ref]), or
- the glvalue is used as the operand of adynamic_cast ([expr.dynamic.cast]) or as the operand oftypeid.
[Note 5:
Therefore, undefined behavior results if an object that is being constructed in one thread is referenced from another thread without adequate synchronization.
— _end note_]
An object is transparently replaceable by an object if
- the storage that occupies exactly overlays the storage that occupied, and
- and are of the same type (ignoring the top-level cv-qualifiers), and
- is not a const, complete object, and
- neither nor is a potentially-overlapping subobject ([intro.object]), and
- either and are both complete objects, or and are direct subobjects of objects and , respectively, and is transparently replaceable by .
After the lifetime of an object has ended and before the storage which the object occupied is reused or released, if a new object is created at the storage location which the original object occupied and the original object was transparently replaceable by the new object, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object.
[Example 3: struct C { int i;void f();const C& operator=( const C& );};const C& C::operator=( const C& other) { if ( this != &other ) { this->~C(); new (this) C(other); f(); } return *this;}C c1; C c2; c1 = c2; c1.f(); — _end example_]
[Note 6:
If these conditions are not met, a pointer to the new object can be obtained from a pointer that represents the address of its storage by calling std::launder ([ptr.launder]).
— _end note_]
If a program ends the lifetime of an object of type T with static ([basic.stc.static]), thread ([basic.stc.thread]), or automatic ([basic.stc.auto]) storage duration and if T has a non-trivial destructor,22and another object of the original type does not occupy that same storage location when the implicit destructor call takes place, the behavior of the program is undefined.
This is true even if the block is exited with an exception.
[Example 4: class T { };struct B { ~B();};void h() { B b;new (&b) T;} — _end example_]
Creating a new object within the storage that a const, complete object with static, thread, or automatic storage duration occupies, or within the storage that such a const object used to occupy before its lifetime ended, results in undefined behavior.
[Example 5: struct B { B();~B();};const B b;void h() { b.~B();new (const_cast<B*>(&b)) const B; } — _end example_]
6.7.5 Indeterminate and erroneous values [basic.indet]
When storage for an object with automatic or dynamic storage duration is obtained, the bytes comprising the storage for the object have the following initial value:
- If the object has dynamic storage duration, or is the object associated with a variable or function parameter whose first declaration is marked with the [[indeterminate]] attribute ([dcl.attr.indet]), the bytes have indeterminate values;
- otherwise, the bytes have erroneous values, where each value is determined by the implementation independently of the state of the program.
If no initialization is performed for an object (including subobjects), such a byte retains its initial value until that value is replaced ([dcl.init.general], [expr.assign]).
If any bit in the value representation has an indeterminate value, the object has an indeterminate value; otherwise, if any bit in the value representation has an erroneous value, the object has an erroneous value ([conv.lval]).
[Note 1:
Objects with static or thread storage duration are zero-initialized, see [basic.start.static].
— _end note_]
Except in the following cases, if an indeterminate value is produced by an evaluation, the behavior is undefined, and if an erroneous value is produced by an evaluation, the behavior is erroneous and the result of the evaluation is the value so produced but is not erroneous:
- If an indeterminate or erroneous value of unsigned ordinary character type or std::byte type is produced by the evaluation of the right operand of a simple assignment operator ([expr.assign]) whose first operand is an lvalue of unsigned ordinary character type or std::byte type, an indeterminate value or that erroneous value, respectively, replaces the value of the object referred to by the left operand.
- If an indeterminate or erroneous value of unsigned ordinary character type is produced by the evaluation of the initialization expression when initializing an object of unsigned ordinary character type, that object is initialized to an indeterminate value or that erroneous value, respectively.
- If an indeterminate value of unsigned ordinary character type or std::byte type is produced by the evaluation of the initialization expression when initializing an object of std::byte type, that object is initialized to an indeterminate value or that erroneous value, respectively.
Converting an indeterminate or erroneous value of unsigned ordinary character type or std::byte type produces an indeterminate or erroneous value, respectively.
In the latter case, the result of the conversion is the value of the converted operand.
[Example 1: int f(bool b) { unsigned char *c = new unsigned char;unsigned char d = *c; int e = d; return b ? d : 0; } int g(bool b) { unsigned char c;unsigned char d = c; assert(c == d); int e = d; return b ? d : 0; } void h() { int d1, d2;int e1 = d1; int e2 = d1; assert(e1 == e2); assert(e1 == d1); assert(e2 == d1); std::memcpy(&d2, &d1, sizeof(int)); assert(e1 == d2); assert(e2 == d2); } — _end example_]
6.7.6 Storage duration [basic.stc]
6.7.6.1 General [basic.stc.general]
The storage duration is the property of an object that defines the minimum potential lifetime of the storage containing the object.
The storage duration is determined by the construct used to create the object and is one of the following:
- static storage duration
- thread storage duration
- automatic storage duration
- dynamic storage duration
[Note 1:
After the duration of a region of storage has ended, the use of pointers to that region of storage is limited ([basic.compound]).
— _end note_]
Static, thread, and automatic storage durations are associated with objects introduced by declarations ([basic.def]) and with temporary objects ([class.temporary]).
The dynamic storage duration is associated with objects created by anew-expression ([expr.new]) or with implicitly created objects ([intro.object]).
The storage duration categories apply to references as well.
The storage duration of subobjects and reference members is that of their complete object ([intro.object]).
6.7.6.2 Static storage duration [basic.stc.static]
All variables which
- do not have thread storage duration and
- belong to a namespace scope ([basic.scope.namespace]) or are first declared with the static or extern keywords ([dcl.stc])
The storage for these entities lasts for the duration of the program ([basic.start.static], [basic.start.term]).
If a variable with static storage duration has initialization or a destructor with side effects, it shall not be eliminated even if it appears to be unused, except that a class object or its copy/move may be eliminated as specified in [class.copy.elision].
[Note 1:
The keyword static can be used to declare a block variable ([basic.scope.block]) with static storage duration;[stmt.dcl] and [basic.start.term] describe the initialization and destruction of such variables.
The keyword static applied to a class data member in a class definition gives the data member static storage duration ([class.static.data]).
— _end note_]
6.7.6.3 Thread storage duration [basic.stc.thread]
All variables declared with the thread_local keyword havethread storage duration.
The storage for these entities lasts for the duration of the thread in which they are created.
There is a distinct object or reference per thread, and use of the declared name refers to the entity associated with the current thread.
[Note 1:
A variable with thread storage duration is initialized as specified in [basic.start.static], [basic.start.dynamic], and [stmt.dcl]and, if constructed, is destroyed on thread exit ([basic.start.term]).
— _end note_]
6.7.6.4 Automatic storage duration [basic.stc.auto]
Variables that belong to a block scope and are not explicitly declared static, thread_local, or extern haveautomatic storage duration.
The storage for such variables lasts until the block in which they are created exits.
[Note 1:
These variables are initialized and destroyed as described in [stmt.dcl].
— _end note_]
Variables that belong to a parameter scope also have automatic storage duration.
The storage for a function parameter lasts until immediately after its destruction ([expr.call]).
If a variable with automatic storage duration has initialization or a destructor with side effects, an implementation shall not destroy it before the end of its block nor eliminate it as an optimization, even if it appears to be unused, except that a class object or its copy/move may be eliminated as specified in [class.copy.elision].
6.7.6.5 Dynamic storage duration [basic.stc.dynamic]
6.7.6.5.1 General [basic.stc.dynamic.general]
A C++ implementation provides access to, and management of, dynamic storage via the global allocation functions operator new andoperator new[] and the global deallocation functions operator delete andoperator delete[].
[Note 1:
The non-allocating forms described in [new.delete.placement]do not perform allocation or deallocation.
— _end note_]
The library provides default definitions for the global allocation and deallocation functions.
Some global allocation and deallocation functions are replaceable ([dcl.fct.def.replace]).
The following allocation and deallocation functions ([support.dynamic]) are implicitly declared in global scope in each translation unit of a program.
void* operator new(std::size_t);void* operator new(std::size_t, std::align_val_t);void operator delete(void*) noexcept;void operator delete(void*, std::size_t) noexcept;void operator delete(void*, std::align_val_t) noexcept;void operator delete(void*, std::size_t, std::align_val_t) noexcept;void* operator new[](std::size_t);void* operator new[](std::size_t, std::align_val_t);void operator delete[](void*) noexcept;void operator delete[](void*, std::size_t) noexcept;void operator delete[](void*, std::align_val_t) noexcept;void operator delete[](void*, std::size_t, std::align_val_t) noexcept;
These implicit declarations introduce only the function namesoperator new,operator new[],operator delete, andoperator delete[].
[Note 2:
The implicit declarations do not introduce the names std,std::size_t,std::align_val_t, or any other names that the library uses to declare these names.
Thus, a new-expression,delete-expression, or function call that refers to one of these functions without importing or including the header or importing a C++ library module ([std.modules]) is well-formed.
However, referring to stdor std::size_tor std::align_val_tis ill-formed unless a standard library declaration ([cstddef.syn], [new.syn], [std.modules]) of that name precedes ([basic.lookup.general]) the use of that name.
— _end note_]
Allocation and/or deallocation functions may also be declared and defined for any class ([class.free]).
If the behavior of an allocation or deallocation function does not satisfy the semantic constraints specified in [basic.stc.dynamic.allocation]and [basic.stc.dynamic.deallocation], the behavior is undefined.
6.7.6.5.2 Allocation functions [basic.stc.dynamic.allocation]
An allocation function that is not a class member function shall belong to the global scope and not have a name with internal linkage.
The return type shall be void*.
The first parameter shall have type std::size_t ([support.types]).
The first parameter shall not have an associated default argument ([dcl.fct.default]).
The value of the first parameter is interpreted as the requested size of the allocation.
An allocation function can be a function template.
Such a template shall declare its return type and first parameter as specified above (that is, template parameter types shall not be used in the return type and first parameter type).
Allocation function templates shall have two or more parameters.
An allocation function attempts to allocate the requested amount of storage.
If it is successful, it returns the address of the start of a block of storage whose length in bytes is at least as large as the requested size.
The order, contiguity, and initial value of storage allocated by successive calls to an allocation function are unspecified.
Even if the size of the space requested is zero, the request can fail.
If the request succeeds, the value returned by a replaceable allocation function is a non-null pointer value ([basic.compound])p0 different from any previously returned value p1, unless that value p1 was subsequently passed to a replaceable deallocation function.
Furthermore, for the library allocation functions in [new.delete.single] and [new.delete.array],p0 represents the address of a block of storage disjoint from the storage for any other object accessible to the caller.
The effect of indirecting through a pointer returned from a request for zero size is undefined.23
For an allocation function other than a reserved placement allocation function ([new.delete.placement]), the pointer returned on a successful call shall represent the address of storage that is aligned as follows:
- If the allocation function takes an argument of type std::align_val_t, the storage will have the alignment specified by the value of this argument.
- Otherwise, if the allocation function is named operator new[], the storage is aligned for any object that does not have new-extended alignment ([basic.align]) and is no larger than the requested size.
- Otherwise, the storage is aligned for any object that does not have new-extended alignment and is of the requested size.
An allocation function that fails to allocate storage can invoke the currently installed new-handler function ([new.handler]), if any.
[Note 1:
A program-supplied allocation function can obtain the currently installed new_handler using thestd::get_new_handler function ([get.new.handler]).
— _end note_]
An allocation function that has a non-throwing exception specification ([except.spec]) indicates failure by returning a null pointer value.
Any other allocation function never returns a null pointer value and indicates failure only by throwing an exception ([except.throw]) of a type that would match a handler ([except.handle]) of typestd::bad_alloc ([bad.alloc]).
A global allocation function is only called as the result of a new expression, or called directly using the function callsyntax, or called indirectly to allocate storage for a coroutine state ([dcl.fct.def.coroutine]), or called indirectly through calls to the functions in the C++ standard library.
[Note 2:
In particular, a global allocation function is not called to allocate storage for objects with static storage duration ([basic.stc.static]), for objects or references with thread storage duration ([basic.stc.thread]), for objects of type std::type_info ([expr.typeid]), for an object of type std::contracts::contract_violationwhen a contract violation occurs ([basic.contract.eval]), or for an exception object ([except.throw]).
— _end note_]
6.7.6.5.3 Deallocation functions [basic.stc.dynamic.deallocation]
A deallocation function that is not a class member function shall belong to the global scope and not have a name with internal linkage.
A deallocation function is a destroying operator deleteif it has at least two parameters and its second parameter is of type std::destroying_delete_t.
A destroying operator delete shall be a class member function named operator delete.
[Note 1:
Array deletion cannot use a destroying operator delete.
— _end note_]
Each deallocation function shall return void.
If the function is a destroying operator delete declared in class type C, the type of its first parameter shall be C*; otherwise, the type of its first parameter shall be void*.
A deallocation function may have more than one parameter.
A usual deallocation function is a deallocation function whose parameters after the first are
- optionally, a parameter of type std::destroying_delete_t, then
- optionally, a parameter of type std::size_t,24then
- optionally, a parameter of type std::align_val_t.
A destroying operator delete shall be a usual deallocation function.
A deallocation function may be an instance of a function template.
Neither the first parameter nor the return type shall depend on a template parameter.
A deallocation function template shall have two or more function parameters.
A template instance is never a usual deallocation function, regardless of its signature.
If a deallocation function terminates by throwing an exception, the behavior is undefined.
The value of the first argument supplied to a deallocation function may be a null pointer value; if so, and if the deallocation function is one supplied in the standard library, the call has no effect.
If the argument given to a deallocation function in the standard library is a pointer that is not the null pointer value ([basic.compound]), the deallocation function shall deallocate the storage referenced by the pointer, ending the duration of the region of storage.
6.7.7 Temporary objects [class.temporary]
Temporary objects are created
- when a prvalue is converted to an xvalue ([conv.rval]) and
- when needed by the implementation to pass or return an object of suitable type (see below).
Even when the creation of the temporary object is unevaluated ([expr.context]), all the semantic restrictions shall be respected as if the temporary object had been created and later destroyed.
[Note 1:
This includes accessibility ([class.access]) and whether it is deleted, for the constructor selected and for the destructor.
However, in the special case of the operand of adecltype-specifier ([dcl.type.decltype]), no temporary is introduced, so the foregoing does not apply to such a prvalue.
— _end note_]
The materialization of a temporary object is generally delayed as long as possible in order to avoid creating unnecessary temporary objects.
[Note 2:
Temporary objects are materialized:
- when binding a reference to a prvalue ([dcl.init.ref], [expr.type.conv], [expr.dynamic.cast], [expr.static.cast], [expr.const.cast], [expr.cast]),
- when performing certain member accesses on a class prvalue ([expr.ref], [expr.mptr.oper]),
- when invoking an implicit object member function on a class prvalue ([expr.call]),
- when performing an array-to-pointer conversion or subscripting on an array prvalue ([conv.array], [expr.sub]),
- when initializing an object of type std::initializer_list<T> from a braced-init-list ([dcl.init.list]),
- for certain unevaluated operands ([expr.typeid], [expr.sizeof]), and
- when a prvalue that has type other than cv void appears as a discarded-value expression ([expr.context]).
— _end note_]
[Example 1:
Consider the following code:class X { public: X(int); X(const X&); X& operator=(const X&);~X();};class Y { public: Y(int); Y(Y&&);~Y();}; X f(X); Y g(Y);void h() { X a(1); X b = f(X(2)); Y c = g(Y(3)); a = f(a);}
X(2) is constructed in the space used to hold f()'s argument andY(3) is constructed in the space used to hold g()'s argument.
Likewise,f()'s result is constructed directly in b andg()'s result is constructed directly in c.
On the other hand, the expressiona = f(a)requires a temporary for the result of f(a), which is materialized so that the reference parameter of X::operator=(const X&) can bind to it.
— _end example_]
When an object of class type Xis passed to or returned from a potentially-evaluated function call, if X is
- a scalar type or
- a class type that has at least one eligible copy or move constructor ([special]), where each such constructor is trivial, and the destructor of X is either trivial or deleted,
implementations are permitted to create temporary objects to hold the function parameter or result object, as follows:
- The first such temporary object is constructed from the function argument or return value, respectively.
- Each successive temporary object is initialized from the previous one as if by direct-initialization if X is a scalar type, otherwise by using an eligible trivial constructor.
- The function parameter or return object is initialized from the final temporary as if by direct-initialization if X is a scalar type, otherwise by using an eligible trivial constructor.
(In all cases, the eligible constructor is used even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object).
[Note 3:
This latitude is granted to allow objects to be passed to or returned from functions in registers.
— _end note_]
Temporary objects are destroyed as the last step in evaluating the full-expression ([intro.execution]) that (lexically) contains the point where they were created.
This is true even if that evaluation ends in throwing an exception.
Thevalue computations andside effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression.
There are five contexts in which temporaries are destroyed at a different point than the end of the full-expression.
The first context is when a default constructor is called to initialize an element of an array with no corresponding initializer ([dcl.init]).
In either case, if the constructor has one or more default arguments, the destruction of every temporary created in a default argument is sequenced before the construction of the next array element, if any.
The third context is when a reference binds to a temporary object.25
The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following:
- a temporary materialization conversion ([conv.rval]),
- ( expression ), where expression is one of these expressions,
- subscripting ([expr.sub]) of an array operand, where that operand is one of these expressions,
- a class member access ([expr.ref]) using the . operator where the left operand is one of these expressions and the right operand designates a non-static data member of non-reference type,
- a pointer-to-member operation ([expr.mptr.oper]) using the .* operator where the left operand is one of these expressions and the right operand is a pointer to data member of non-reference type,
- a
- const_cast ([expr.const.cast]),
- static_cast ([expr.static.cast]),
- dynamic_cast ([expr.dynamic.cast]), or
- reinterpret_cast ([expr.reinterpret.cast])
converting, without a user-defined conversion, a glvalue operand that is one of these expressions to a glvalue that refers to the object designated by the operand, or to its complete object or a subobject thereof,
- a conditional expression ([expr.cond]) that is a glvalue where the second or third operand is one of these expressions, or
- a comma expression ([expr.comma]) that is a glvalue where the right operand is one of these expressions.
[Example 2: template<typename T> using id = T;int i = 1;int&& a = id<int[3]>{1, 2, 3}[i]; const int& b = static_cast<const int&>(0); int&& c = cond ? id<int[3]>{1, 2, 3}[i] : static_cast<int&&>(0); — _end example_]
[Note 4:
An explicit type conversion ([expr.type.conv], [expr.cast]) is interpreted as a sequence of elementary casts, covered above.
[Example 3: const int& x = (const int&)1; — _end example_]
— _end note_]
[Note 5:
If a temporary object has a reference member initialized by another temporary object, lifetime extension applies recursively to such a member's initializer.
[Example 4: struct S { const int& m;};const S& s = S{1}; — _end example_]
— _end note_]
The exceptions to this lifetime rule are:
- A temporary object bound to a reference parameter in a function call ([expr.call]) persists until the completion of the full-expression containing the call.
- A temporary object bound to a reference element of an aggregate of class type initialized from a parenthesized expression-list ([dcl.init]) persists until the completion of the full-expression containing the expression-list.
- [Note 6:
This might introduce a dangling reference.
— end note_]
[_Example 5: struct S { int mi; const std::pair<int,int>& mp; }; S a { 1, {2,3} }; S* p = new S{ 1, {2,3} }; — _end example_]
The fourth context is when a temporary object is created in the for-range-initializer of a range-based for statement.
If such a temporary object would otherwise be destroyed at the end of the for-range-initializer full-expression, the object persists for the lifetime of the reference initialized by the for-range-initializer.
The fifth context is when a temporary object is created in a structured binding declaration ([dcl.struct.bind]).
Any temporary objects introduced by the initializers for the variables with unique names are destroyed at the end of the structured binding declaration.
Let x and y each be either a temporary object whose lifetime is not extended, or a function parameter.
If the lifetimes of x and y end at the end of the same full-expression, andx is initialized before y, then the destruction of y is sequenced before that of x.
If the lifetime of two or more temporaries with lifetimes extending beyond the full-expressions in which they were created ends at the same point, these temporaries are destroyed at that point in the reverse order of the completion of their construction.
In addition, the destruction of such temporaries shall take into account the ordering of destruction of objects with static, thread, or automatic storage duration ([basic.stc.static], [basic.stc.thread], [basic.stc.auto]); that is, ifobj1is an object with the same storage duration as the temporary and created before the temporary is created the temporary shall be destroyed beforeobj1is destroyed; ifobj2is an object with the same storage duration as the temporary and created after the temporary is created the temporary shall be destroyed afterobj2is destroyed.
[Example 6: struct S { S(); S(int);friend S operator+(const S&, const S&);~S();}; S obj1;const S& cr = S(16)+S(23); S obj2;
The expressionS(16) + S(23)creates three temporaries: a first temporaryT1to hold the result of the expressionS(16), a second temporaryT2to hold the result of the expressionS(23), and a third temporaryT3to hold the result of the addition of these two expressions.
The temporaryT3is then bound to the referencecr.
It is unspecified whetherT1orT2is created first.
On an implementation whereT1is created beforeT2,T2shall be destroyed beforeT1.
The temporariesT1andT2are bound to the reference parameters ofoperator+; these temporaries are destroyed at the end of the full-expression containing the call tooperator+.
The temporaryT3bound to the referencecris destroyed at the end ofcr's lifetime, that is, at the end of the program.
In addition, the order in whichT3is destroyed takes into account the destruction order of other objects with static storage duration.
That is, becauseobj1is constructed beforeT3, andT3is constructed beforeobj2,obj2shall be destroyed beforeT3, andT3shall be destroyed beforeobj1.
— _end example_]
6.8 Types [basic.types]
6.8.1 General [basic.types.general]
[Note 1:
[basic.types] and the subclauses thereof impose requirements on implementations regarding the representation of types.
There are two kinds of types: fundamental types and compound types.
— _end note_]
For any object (other than a potentially-overlapping subobject) of trivially copyable typeT, whether or not the object holds a valid value of typeT, the underlying bytes ([intro.memory]) making up the object can be copied into an array ofchar,unsigned char, orstd::byte ([cstddef.syn]).26
If the content of that array is copied back into the object, the object shall subsequently hold its original value.
[Example 1: constexpr std::size_t N = sizeof(T);char buf[N]; T obj; std::memcpy(buf, &obj, N); std::memcpy(&obj, buf, N); — _end example_]
For two distinct objects obj1 and obj2of trivially copyable type T, where neither obj1 nor obj2 is a potentially-overlapping subobject, if the underlying bytes ([intro.memory]) making upobj1 are copied into obj2,27 obj2 shall subsequently hold the same value asobj1.
[Example 2: T* t1p; T* t2p; std::memcpy(t1p, t2p, sizeof(T)); — _end example_]
The object representationof a complete object type T is the sequence of N unsigned char objects taken up by a non-bit-field complete object of type T, where N equalssizeof(T).
The value representationof a type T is the set of bits in the object representation of Tthat participate in representing a value of type T.
The object and value representation of a non-bit-field complete object of type T are the bytes and bits, respectively, of the object corresponding to the object and value representation of its type.
The object representation of a bit-field object is the sequence of N bits taken up by the object, where N is the width of the bit-field ([class.bit]).
The value representation of a bit-field object is the set of bits in the object representation that participate in representing its value.
Bits in the object representation of a type or object that are not part of the value representation are padding bits.
For trivially copyable types, the value representation is a set of bits in the object representation that determines avalue, which is one discrete element of animplementation-defined set of values.28
A class that has been declared but not defined, an enumeration type in certain contexts ([dcl.enum]), or an array of unknown bound or of incomplete element type, is anincompletely-defined object type.29
Incompletely-defined object types and cv void areincomplete types ([basic.fundamental]).
[Note 2:
Objects cannot be defined to have an incomplete type ([basic.def]).
— _end note_]
A class type (such as “class X”) can be incomplete at one point in a translation unit and complete later on; the type “class X” is the same type at both points.
The declared type of an array object can be an array of incomplete class type and therefore incomplete; if the class type is completed later on in the translation unit, the array type becomes complete; the array type at those two points is the same type.
The declared type of an array object can be an array of unknown bound and therefore be incomplete at one point in a translation unit and complete later on; the array types at those two points (“array of unknown bound of T” and “array ofN T”) are different types.
[Note 3:
The type of a pointer or reference to array of unknown bound permanently points to or refers to an incomplete type.
An array of unknown bound named by a typedef declaration permanently refers to an incomplete type.
In either case, the array type cannot be completed.
— _end note_]
[Example 3: class X; extern X* xp; extern int arr[]; typedef int UNKA[]; UNKA* arrp; UNKA** arrpp;void foo() { xp++; arrp++; arrpp++; } struct X { int i; }; int arr[10]; X x;void bar() { xp = &x; arrp = &arr; xp++; arrp++; } — _end example_]
[Note 4:
The rules for declarations and expressions describe in which contexts incomplete types are prohibited.
— _end note_]
An object type is a (possibly cv-qualified) type that is not a function type, not a reference type, and not cv void.
Arithmetic types ([basic.fundamental]), enumeration types, pointer types, pointer-to-member types ([basic.compound]),std::nullptr_t, andcv-qualified versions of these types are collectively calledscalar types.
Scalar types, trivially copyable class types ([class.prop]), arrays of such types, and cv-qualified versions of these types are collectively called trivially copyable types.
Scalar types, trivially relocatable class types ([class.prop]), arrays of such types, and cv-qualified versions of these types are collectively called trivially relocatable types.
Cv-unqualified scalar types, replaceable class types ([class.prop]), and arrays of such types are collectively calledreplaceable types.
Scalar types, standard-layout class types ([class.prop]), arrays of such types, and cv-qualified versions of these types are collectively called standard-layout types.
Scalar types, implicit-lifetime class types ([class.prop]), array types, and cv-qualified versions of these types are collectively called implicit-lifetime types.
A type is a literal type if it is:
- cv void; or
- a scalar type; or
- a reference type; or
- an array of literal type; or
- a possibly cv-qualified class type that has all of the following properties:
- it has a constexpr destructor ([dcl.constexpr]),
- all of its non-variant non-static data members and base classes are of non-volatile literal types, and
- it
* is a closure type ([expr.prim.lambda.closure]),
* is an aggregate union type that has either no variant members or at least one variant member of non-volatile literal type,
* is a non-union aggregate type for which each of its anonymous union members satisfies the above requirements for an aggregate union type, or
* has at least one constexpr constructor or constructor template (possibly inherited ([namespace.udecl]) from a base class) that is not a copy or move constructor.
[Note 5:
A literal type is one for which it might be possible to create an object within a constant expression.
It is not a guarantee that it is possible to create such an object, nor is it a guarantee that any object of that type will be usable in a constant expression.
— _end note_]
6.8.2 Fundamental types [basic.fundamental]
There are five standard signed integer types:“signed char”, “short int”, “int”, “long int”, and “long long int”.
In this list, each type provides at least as much storage as those preceding it in the list.
There may also be implementation-definedextended signed integer types.
The standard and extended signed integer types are collectively calledsigned integer types.
The range of representable values for a signed integer type is to (inclusive), where N is called the width of the type.
[Note 1:
Plain ints are intended to have the natural width suggested by the architecture of the execution environment; the other signed integer types are provided to meet special needs.
— _end note_]
For each of the standard signed integer types, there exists a corresponding (but different)standard unsigned integer type:“unsigned char”, “unsigned short int”, “unsigned int”, “unsigned long int”, and “unsigned long long int”.
Likewise, for each of the extended signed integer types, there exists a corresponding extended unsigned integer type.
The standard and extended unsigned integer types are collectively called unsigned integer types.
An unsigned integer type has the same width _N_as the corresponding signed integer type.
The range of representable values for the unsigned type is0 to (inclusive); arithmetic for the unsigned type is performed modulo .
[Note 2:
Unsigned arithmetic does not overflow.
Overflow for signed arithmetic yields undefined behavior ([expr.pre]).
— _end note_]
An unsigned integer type has the same object representation, value representation, and alignment requirements ([basic.align]) as the corresponding signed integer type.
For each value x of a signed integer type, the value of the corresponding unsigned integer type congruent to x modulo has the same value of corresponding bits in its value representation.30
[Example 1:
The value of a signed integer type has the same representation as the largest value of the corresponding unsigned type.
— _end example_]
The width of each standard signed integer type shall not be less than the values specified in Table 14.
The value representation of a signed or unsigned integer type comprises N bits, where N is the respective width.
Each set of values for any padding bits ([basic.types.general]) in the object representation are alternative representations of the value specified by the value representation.
[Note 3:
Padding bits have unspecified value, but cannot cause traps.
In contrast, see ISO/IEC 9899:2018 6.2.6.2.
— _end note_]
[Note 4:
The signed and unsigned integer types satisfy the constraints given in ISO/IEC 9899:2018 5.2.4.2.1.
— _end note_]
Except as specified above, the width of a signed or unsigned integer type isimplementation-defined.
Each value x of an unsigned integer type with width N has a unique representation , where each coefficient is either 0 or 1; this is called the base-2 representation of x.
The base-2 representation of a value of signed integer type is the base-2 representation of the congruent value of the corresponding unsigned integer type.
The standard signed integer types and standard unsigned integer types are collectively called the standard integer types, and the extended signed integer types and extended unsigned integer types are collectively called theextended integer types.
A fundamental type specified to have a signed or unsigned integer type as its underlying type has the same object representation, value representation, alignment requirements ([basic.align]), and range of representable values as the underlying type.
Further, each value has the same representation in both types.
Type char is a distinct type that has an implementation-defined choice of “signed char” or “unsigned char” as its underlying type.
The three types char, signed char, and unsigned charare collectively calledordinary character types.
The ordinary character types and char8_tare collectively called narrow character types.
For narrow character types, each possible bit pattern of the object representation represents a distinct value.
[Note 5:
This requirement does not hold for other types.
— _end note_]
[Note 6:
A bit-field of narrow character type whose width is larger than the width of that type has padding bits; see [basic.types.general].
— _end note_]
Type wchar_t is a distinct type that has an implementation-defined signed or unsigned integer type as its underlying type.
Type char8_t denotes a distinct type whose underlying type is unsigned char.
Types char16_t and char32_t denote distinct types whose underlying types are uint_least16_t and uint_least32_t, respectively, in .
Type bool is a distinct type that has the same object representation, value representation, and alignment requirements as an implementation-defined unsigned integer type.
The values of type bool aretrue and false.
[Note 7:
There are no signed, unsigned,short, or long bool types or values.
— _end note_]
The types char, wchar_t,char8_t, char16_t, and char32_tare collectively called character types.
The character types, bool, the signed and unsigned integer types, and cv-qualified versions ([basic.type.qualifier]) thereof, are collectively termedintegral types.
A synonym for integral type is integer type.
[Note 8:
Enumerations ([dcl.enum]) are not integral; however, unscoped enumerations can be promoted to integral types as specified in [conv.prom].
— _end note_]
The three distinct typesfloat,double, andlong doublecan represent floating-point numbers.
The type double provides at least as much precision as float, and the type long double provides at least as much precision as double.
The set of values of the typefloat is a subset of the set of values of the typedouble; the set of values of the type double is a subset of the set of values of the type long double.
The typesfloat, double, and long double, and cv-qualified versions ([basic.type.qualifier]) thereof, are collectively termedstandard floating-point types.
An implementation may also provide additional types that represent floating-point values and define them (and cv-qualified versions thereof) to beextended floating-point types.
The standard and extended floating-point types are collectively termed floating-point types.
[Note 9:
Any additional implementation-specific types representing floating-point values that are not defined by the implementation to be extended floating-point types are not considered to be floating-point types, and this document imposes no requirements on them or their interactions with floating-point types.
— _end note_]
Except as specified in [basic.extended.fp], the object and value representations and accuracy of operations of floating-point types are implementation-defined.
The minimum range of representable values for a floating-point type is the most negative finite floating-point number representable in that type through the most positive finite floating-point number representable in that type.
In addition, if negative infinity is representable in a type, the range of that type is extended to all negative real numbers; likewise, if positive infinity is representable in a type, the range of that type is extended to all positive real numbers.
[Note 10:
Since negative and positive infinity are representable in ISO/IEC 60559 formats, all real numbers lie within the range of representable values of a floating-point type adhering to ISO/IEC 60559.
— _end note_]
Integral and floating-point types are collectively termed arithmetic types.
[Note 11:
Properties of the arithmetic types, such as their minimum and maximum representable value, can be queried using the facilities in the standard library headers,, and.
— _end note_]
A type cv voidis an incomplete type that cannot be completed; such a type has an empty set of values.
It is used as the return type for functions that do not return a value.
An expression of type cv void shall be used only as
- an expression statement ([stmt.expr]),
- the expression in a return statement ([stmt.return]) for a function with the return type cv void,
- an operand of a comma expression ([expr.comma]),
- the second or third operand of ?: ([expr.cond]),
- the operand of a typeid expression ([expr.typeid]),
- the operand of a noexcept operator ([expr.unary.noexcept]),
- the operand of a decltype specifier ([dcl.type.decltype]), or
- the operand of an explicit conversion to typecv void ([expr.type.conv], [expr.static.cast], [expr.cast]).
The types denoted by cv std::nullptr_t are distinct types.
A prvalue of type std::nullptr_t is a null pointer constant ([conv.ptr]).
Such values participate in the pointer and the pointer-to-member conversions ([conv.ptr], [conv.mem]).
sizeof(std::nullptr_t) shall be equal to sizeof(void*).
The types described in this subclause are called fundamental types.
[Note 12:
Even if the implementation defines two or more fundamental types to have the same value representation, they are nevertheless different types.
— _end note_]
6.8.3 Optional extended floating-point types [basic.extended.fp]
If the implementation supports an extended floating-point type ([basic.fundamental]) whose properties are specified by the ISO/IEC 60559 floating-point interchange format binary16, then the typedef-name std::float16_tis declared in the header and names such a type, the macro __STDCPP_FLOAT16_T__ is defined ([cpp.predefined]), and the floating-point literal suffixes f16 and F16are supported ([lex.fcon]).
If the implementation supports an extended floating-point type whose properties are specified by the ISO/IEC 60559 floating-point interchange format binary32, then the typedef-name std::float32_tis declared in the header and names such a type, the macro __STDCPP_FLOAT32_T__ is defined, and the floating-point literal suffixes f32 and F32 are supported.
If the implementation supports an extended floating-point type whose properties are specified by the ISO/IEC 60559 floating-point interchange format binary64, then the typedef-name std::float64_tis declared in the header and names such a type, the macro __STDCPP_FLOAT64_T__ is defined, and the floating-point literal suffixes f64 and F64 are supported.
If the implementation supports an extended floating-point type whose properties are specified by the ISO/IEC 60559 floating-point interchange format binary128, then the typedef-name std::float128_tis declared in the header and names such a type, the macro __STDCPP_FLOAT128_T__ is defined, and the floating-point literal suffixes f128 and F128 are supported.
If the implementation supports an extended floating-point type with the properties, as specified by ISO/IEC 60559, of radix (b) of 2, storage width in bits (k) of 16, precision in bits (p) of 8, maximum exponent (emax) of 127, and exponent field width in bits (w) of 8, then the typedef-name std::bfloat16_tis declared in the header and names such a type, the macro __STDCPP_BFLOAT16_T__ is defined, and the floating-point literal suffixes bf16 and BF16 are supported.
[Note 1:
A summary of the parameters for each type is given in Table 15.
The precision p includes the implicit 1 bit at the beginning of the significand, so the storage used for the significand is bits.
ISO/IEC 60559 does not assign a name for a type having the parameters specified for std::bfloat16_t.
— _end note_]
Table 15 — Properties of named extended floating-point types [tab:basic.extended.fp]
🔗Parameter | float16_t | float32_t | float64_t | float128_t | bfloat16_t |
---|---|---|---|---|---|
🔗ISO/IEC 60559 name | binary16 | binary32 | binary64 | binary128 | |
🔗k, storage width in bits | 16 | 32 | 64 | 128 | 16 |
🔗p, precision in bits | 11 | 24 | 53 | 113 | 8 |
🔗emax, maximum exponent | 15 | 127 | 1023 | 16383 | 127 |
🔗w, exponent field width in bits | 5 | 8 | 11 | 15 | 8 |
Recommended practice: Any names that the implementation provides for the extended floating-point types described in this subsection that are in addition to the names declared in the header should be chosen to increase compatibility and interoperability with the interchange types_Float16, _Float32, _Float64, and _Float128defined in ISO/IEC TS 18661-3 and with future versions of ISO/IEC 9899.
6.8.4 Compound types [basic.compound]
Compound types can be constructed in the following ways:
- functions, which have parameters of given types and returnvoid or a result of a given type, [dcl.fct];
- pointers to cv void or objects or functions (including static members of classes) of a given type, [dcl.ptr];
- references to objects or functions of a given type, [dcl.ref].
There are two types of references:- lvalue reference
- rvalue reference
- unions, which are classes capable of containing objects of different types at different times, [class.union];
- enumerations, which comprise a set of named constant values, [dcl.enum];
- pointers to non-static class members,31which identify members of a given type within objects of a given class, [dcl.mptr].
Pointers to data members and pointers to member functions are collectively called pointer-to-member types.
These methods of constructing types can be applied recursively; restrictions are mentioned in [dcl.meaning].
Constructing a type such that the number of bytes in its object representation exceeds the maximum value representable in the type std::size_t ([support.types]) is ill-formed.
The type of a pointer to cv void or a pointer to an object type is called an object pointer type.
[Note 1:
A pointer to voiddoes not have a pointer-to-object type, however, because void is not an object type.
— _end note_]
The type of a pointer that can designate a function is called a function pointer type.
A pointer to an object of type T is referred to as a “pointer toT”.
[Example 1:
A pointer to an object of type int is referred to as “pointer to int” and a pointer to an object of class X is called a “pointer to X”.
— _end example_]
Except for pointers to static members, text referring to “pointers” does not apply to pointers to members.
Pointers to incomplete types are allowed although there are restrictions on what can be done with them ([basic.types.general]).
Every value of pointer type is one of the following:
- a pointer to an object or function (the pointer is said to point to the object or function), or
- a pointer past the end of an object ([expr.add]), or
- the null pointer value for that type, or
- an invalid pointer value.
A value of a pointer type that is a pointer to or past the end of an objectrepresents the address of the first byte in memory ([intro.memory]) occupied by the object32or the first byte in memory after the end of the storage occupied by the object, respectively.
[Note 2:
A pointer past the end of an object ([expr.add]) is not considered to point to an unrelated object of the object's type, even if the unrelated object is located at that address.
— _end note_]
For purposes of pointer arithmetic ([expr.add]) and comparison ([expr.rel], [expr.eq]), a pointer past the end of the last element of an array x of n elements is considered to be equivalent to a pointer to a hypothetical array element n of x, and an object of type T that is not an array element is considered to belong to an array with one element of type T.
The value representation of pointer types is implementation-defined.
Pointers to layout-compatible types shall have the same value representation and alignment requirements ([basic.align]).
[Note 3:
Pointers to over-aligned types have no special representation, but their range of valid values is restricted by the extended alignment requirement.
— _end note_]
A pointer value P isvalid in the context of an evaluation Eif P is a pointer to function or a null pointer value, or if it is a pointer to or past the end of an object O andE happens before the end of the duration of the region of storage for O.
If a pointer value P is used in an evaluation E andP is not valid in the context of E, then the behavior is undefined if E is an indirection ([expr.unary.op]) or an invocation of a deallocation function ([basic.stc.dynamic.deallocation]), and implementation-defined otherwise.33
[Note 4:
P can be valid in the context of E even if it points to a type unrelated to that of O or if O is not within its lifetime, although further restrictions apply to such pointer values ([basic.life], [basic.lval], [expr.add]).
— _end note_]
Two objects a and b are pointer-interconvertible if
- they are the same object, or
- one is a union object and the other is a non-static data member of that object ([class.union]), or
- one is a standard-layout class object and the other is the first non-static data member of that object or any base class subobject of that object ([class.mem]), or
- there exists an object c such that_a_ and c are pointer-interconvertible, and_c_ and b are pointer-interconvertible.
If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_cast ([expr.reinterpret.cast]).
[Note 5:
An array object and its first element are not pointer-interconvertible, even though they have the same address.
— _end note_]
A byte of storage _b_is reachable througha pointer value that points to an object x_if there is an object y, pointer-interconvertible with x, such that b is within the storage occupied by_y, or the immediately-enclosing array object if y is an array element.
A pointer to cv voidcan be used to point to objects of unknown type.
Such a pointer shall be able to hold any object pointer.
An object of type “pointer to cv void” shall have the same representation and alignment requirements as an object of type “pointer to cv char”.
6.8.5 CV-qualifiers [basic.type.qualifier]
Each type other than a function or reference type is part of a group of four distinct, but related, types: a cv-unqualified version, a const-qualified version, a volatile-qualified version, and a const-volatile-qualified version.
The types in each such group shall have the same representation and alignment requirements ([basic.align]).34
A function or reference type is always cv-unqualified.
- A const object is an object of type const T or a non-mutable subobject of a const object.
- A volatile object is an object of typevolatile T or a subobject of a volatile object.
- A const volatile object is an object of typeconst volatile T, a non-mutable subobject of a const volatile object, a const subobject of a volatile object, or a non-mutable volatile subobject of a const object.
Except for array types, a compound type ([basic.compound]) is not cv-qualified by the cv-qualifiers (if any) of the types from which it is compounded.
An array type whose elements are cv-qualified is also considered to have the same cv-qualifications as its elements.
[Note 2:
Cv-qualifiers applied to an array type attach to the underlying element type, so the notation “cv T”, where T is an array type, refers to an array whose elements are so-qualified ([dcl.array]).
— _end note_]
[Example 1: typedef char CA[5];typedef const char CC; CC arr1[5] = { 0 };const CA arr2 = { 0 };
The type of both arr1 and arr2 is “array of 5const char”, and the array type is considered to be const-qualified.
— _end example_]
There is a partial ordering on cv-qualifiers, so that a type can be said to be more cv-qualified than another.
Table 16 shows the relations that constitute this ordering.
Table 16 — Relations on const and volatile [tab:basic.type.qualifier.rel]
🔗no cv-qualifier | < | const |
---|---|---|
🔗no cv-qualifier | < | volatile |
🔗no cv-qualifier | < | const volatile |
🔗const | < | const volatile |
🔗volatile | < | const volatile |
In this document, the notation cv (or_cv1_, cv2, etc.)
, used in the description of types, represents an arbitrary set of cv-qualifiers, i.e., one of {const}, {volatile}, {const,volatile}, or the empty set.
For a type cv T, the top-level cv-qualifiersof that type are those denoted by cv.
[Example 2:
The type corresponding to the type-id const int&has no top-level cv-qualifiers.
The type corresponding to the type-id volatile int * consthas the top-level cv-qualifier const.
For a class type C, the type corresponding to the type-id void (C::* volatile)(int) consthas the top-level cv-qualifier volatile.
— _end example_]
6.8.6 Conversion ranks [conv.rank]
Every integer type has an integer conversion rank defined as follows:
- No two signed integer types other than char and signed char (if char is signed) have the same rank, even if they have the same representation.
- The rank of a signed integer type is greater than the rank of any signed integer type with a smaller width.
- The rank of long long int is greater than the rank of long int, which is greater than the rank of int, which is greater than the rank ofshort int, which is greater than the rank ofsigned char.
- The rank of any unsigned integer type equals the rank of the corresponding signed integer type.
- The rank of any standard integer type is greater than the rank of any extended integer type with the same width.
- The rank of char equals the rank of signed charand unsigned char.
- The rank of bool is less than the rank of all standard integer types.
- The ranks of char8_t, char16_t, char32_t, andwchar_t equal the ranks of their underlying types ([basic.fundamental]).
- The rank of any extended signed integer type relative to another extended signed integer type with the same width is implementation-defined, but still subject to the other rules for determining the integer conversion rank.
- For all integer types T1, T2, and T3, ifT1 has greater rank than T2 and T2 has greater rank than T3, then T1 has greater rank thanT3.
[Note 1:
The integer conversion rank is used in the definition of the integral promotions ([conv.prom]) and the usual arithmetic conversions ([expr.arith.conv]).
— _end note_]
Every floating-point type has a floating-point conversion rankdefined as follows:
- The rank of a floating point type T is greater than the rank of any floating-point type whose set of values is a proper subset of the set of values of T.
- The rank of long double is greater than the rank of double, which is greater than the rank of float.
- Two extended floating-point types with the same set of values have equal ranks.
- An extended floating-point type with the same set of values as exactly one cv-unqualified standard floating-point type has a rank equal to the rank of that standard floating-point type.
- An extended floating-point type with the same set of values as more than one cv-unqualified standard floating-point type has a rank equal to the rank of double.
[Note 2:
The treatment of std::float64_t differs from that of the analogous _Float64 in C, for example on platforms where all oflong double,double, andstd::float64_thave the same set of values (see ISO/IEC 9899:2024 H.4.2).
— _end note_]
[Note 3:
The conversion ranks of floating-point types T1 and T2are unordered if the set of values of T1 is neither a subset nor a superset of the set of values of T2.
This can happen when one type has both a larger range and a lower precision than the other.
— _end note_]
Floating-point types that have equal floating-point conversion ranks are ordered by floating-point conversion subrank.
The subrank forms a total order among types with equal ranks.
The typesstd::float16_t,std::float32_t,std::float64_t, andstd::float128_t ([stdfloat.syn]) have a greater conversion subrank than any standard floating-point type with equal conversion rank.
Otherwise, the conversion subrank order isimplementation-defined.
[Note 4:
The floating-point conversion rank and subrank are used in the definition of the usual arithmetic conversions ([expr.arith.conv]).
— _end note_]
6.9 Program execution [basic.exec]
6.9.1 Sequential execution [intro.execution]
An instance of each object with automatic storage duration is associated with each entry into its block.
Such an object exists and retains its last-stored value during the execution of the block and while the block is suspended (by a call of a function, suspension of a coroutine ([expr.await]), or receipt of a signal).
A constituent expression is defined as follows:
- The constituent expression of an expression is that expression.
- The constituent expression of a conversion is the corresponding implicit function call, if any, or the converted expression otherwise.
- The constituent expressions of a braced-init-list or of a (possibly parenthesized) expression-listare the constituent expressions of the elements of the respective list.
[Example 1: struct A { int x; };struct B { int y; struct A a; }; B b = { 5, { 1+1 } };
The constituent expressions of the initializerused for the initialization of b are 5 and 1+1.
— _end example_]
The immediate subexpressions of an expression E are
- the constituent expressions of E's operands ([expr.prop]),
- any function call that E implicitly invokes,
- if E is a lambda-expression ([expr.prim.lambda]), the initialization of the entities captured by copy and the constituent expressions of the initializer of the init-captures,
- if E is a function call or implicitly invokes a function, the constituent expressions of each default argument ([dcl.fct.default]) used in the call, or
- if E creates an aggregate object ([dcl.init.aggr]), the constituent expressions of each default member initializer ([class.mem]) used in the initialization.
A subexpression of an expression E is an immediate subexpression of E or a subexpression of an immediate subexpression of E.
The potentially-evaluated subexpressions of an expression, conversion, or initializer E are
- the constituent expressions of E and
- the subexpressions thereof that are not subexpressions of a nested unevaluated operand ([expr.context]).
A full-expression is
- an unevaluated operand,
- a constant-expression ([expr.const]),
- an immediate invocation ([expr.const]),
- an init-declarator ([dcl.decl]) (including such introduced by a structured binding ([dcl.struct.bind])) or a mem-initializer ([class.base.init]), including the constituent expressions of the initializer,
- an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object ([class.temporary]) whose lifetime has not been extended,
- the predicate of a contract assertion ([basic.contract]), or
- an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition.
Conversions applied to the result of an expression in order to satisfy the requirements of the language construct in which the expression appears are also considered to be part of the full-expression.
For an initializer, performing the initialization of the entity (including evaluating default member initializers of an aggregate) is also considered part of the full-expression.
[Example 2: struct S { S(int i): I(i) { } int& v() { return I; } ~S() noexcept(false) { } private: int I;}; S s1(1); void f() { S s2 = 2; if (S(3).v()) { } bool b = noexcept(S(4)); } struct B { B(S = S(0));}; B b[2] = { B(), B() }; — _end example_]
[Note 2:
The evaluation of a full-expression can include the evaluation of subexpressions that are not lexically part of the full-expression.
For example, subexpressions involved in evaluating default arguments ([dcl.fct.default]) are considered to be created in the expression that calls the function, not the expression that defines the default argument.
— _end note_]
Reading an object designated by a volatileglvalue ([basic.lval]), modifying an object, calling a library I/O function, or calling a function that does any of those operations are allside effects, which are changes in the state of the execution environment.
Evaluation of an expression (or a subexpression) in general includes both value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation) and initiation of side effects.
When a call to a library I/O function returns or an access through a volatile glvalue is evaluated, the side effect is considered complete, even though some external actions implied by the call (such as the I/O itself) or by the volatile access may not have completed yet.
Sequenced before is an asymmetric, transitive, pair-wise relation between evaluations executed by a single thread ([intro.multithread]), which induces a partial order among those evaluations.
Given any two evaluations A and_B_, if A is sequenced before B(or, equivalently, B is sequenced after A), then the execution of_A_ shall precede the execution of B.
If A is not sequenced before B and B is not sequenced before A, then A and_B_ are unsequenced.
[Note 3:
The execution of unsequenced evaluations can overlap.
— _end note_]
Evaluations A and B areindeterminately sequenced when either A is sequenced before_B_ or B is sequenced before A, but it is unspecified which.
[Note 4:
Indeterminately sequenced evaluations cannot overlap, but either can be executed first.
— _end note_]
An expression _X_is said to be sequenced before an expression Y if every value computation and every side effect associated with the expression _X_is sequenced before every value computation and every side effect associated with the expression Y.
Everyvalue computation andside effect associated with a full-expression is sequenced before every value computation and side effect associated with the next full-expression to be evaluated.35
Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.
[Note 5:
In an expression that is evaluated more than once during the execution of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations.
— _end note_]
The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.
The behavior is undefined if
- a side effect on a memory location ([intro.memory]) or
- starting or ending the lifetime of an object in a memory location
is unsequenced relative to
- another side effect on the same memory location,
- starting or ending the lifetime of an object occupying storage that overlaps with the memory location, or
- a value computation using the value of any object in the same memory location,
and the two evaluations are not potentially concurrent ([intro.multithread]).
[Note 6:
Starting the lifetime of an object in a memory location can end the lifetime of objects in other memory locations ([basic.life]).
— _end note_]
[Note 7:
The next subclause imposes similar, but more complex restrictions on potentially concurrent computations.
— _end note_]
[Example 3: void g(int i) { i = 7, i++, i++; i = i++ + 1; i = i++ + i; i = i + 1; union U { int x, y; } u;(u.x = 1, 0) + (u.y = 2, 0); } — _end example_]
When invoking a function f (whether or not the function is inline), every argument expression and the postfix expression designating _f_are sequenced before every precondition assertion of f ([dcl.contract.func]), which in turn are sequenced before every expression or statement in the body of f, which in turn are sequenced before every postcondition assertion of f.
For each
- function invocation,
- evaluation of an await-expression ([expr.await]), or
- evaluation of a throw-expression ([expr.throw])
F, each evaluation that does not occur within _F_but is evaluated on the same thread and as part of the same signal handler (if any) is either sequenced before all evaluations that occur within _F_or sequenced after all evaluations that occur within F;36if F invokes or resumes a coroutine ([expr.await]), only evaluations subsequent to the previous suspension (if any) and prior to the next suspension (if any) are considered to occur within F.
Several contexts in C++ cause evaluation of a function call, even though no corresponding function call syntax appears in the translation unit.
[Example 4:
Evaluation of a new-expression invokes one or more allocation and constructor functions; see [expr.new].
For another example, invocation of a conversion function ([class.conv.fct]) can arise in contexts in which no function call syntax appears.
— _end example_]
The sequencing constraints on the execution of the called function (as described above) are features of the function calls as evaluated, regardless of the syntax of the expression that calls the function.
If a signal handler is executed as a result of a call to the std::raisefunction, then the execution of the handler is sequenced after the invocation of the std::raise function and before its return.
[Note 8:
When a signal is received for another reason, the execution of the signal handler is usually unsequenced with respect to the rest of the program.
— _end note_]
6.9.2 Multi-threaded executions and data races [intro.multithread]
6.9.2.1 General [intro.multithread.general]
A thread of execution (also known as a thread) is a single flow of control within a program, including the initial invocation of a specific top-level function, and recursively including every function invocation subsequently executed by the thread.
[Note 1:
When one thread creates another, the initial call to the top-level function of the new thread is executed by the new thread, not by the creating thread.
— _end note_]
Every thread in a program can potentially access every object and function in a program.37
Under a hosted implementation, a C++ program can have more than one thread running concurrently.
The execution of each thread proceeds as defined by the remainder of this document.
The execution of the entire program consists of an execution of all of its threads.
[Note 2:
Usually the execution can be viewed as an interleaving of all its threads.
However, some kinds of atomic operations, for example, allow executions inconsistent with a simple interleaving, as described below.
— _end note_]
Under a freestanding implementation, it is implementation-defined whether a program can have more than one thread of execution.
For a signal handler that is not executed as a result of a call to thestd::raise function, it is unspecified which thread of execution contains the signal handler invocation.
6.9.2.2 Data races [intro.races]
The value of an object visible to a thread T at a particular point is the initial value of the object, a value assigned to the object by T, or a value assigned to the object by another thread, according to the rules below.
[Note 1:
In some cases, there might instead be undefined behavior.
Much of this subclause is motivated by the desire to support atomic operations with explicit and detailed visibility constraints.
However, it also implicitly supports a simpler view for more restricted programs.
— _end note_]
Two expression evaluations conflict if one of them
- modifies ([defns.access]) a memory location ([intro.memory]) or
- starts or ends the lifetime of an object in a memory location
and the other one
- reads or modifies the same memory location or
- starts or ends the lifetime of an object occupying storage that overlaps with the memory location.
[Note 2:
A modification can still conflict even if it does not alter the value of any bits.
— _end note_]
The library defines a number of atomic operations ([atomics]) and operations on mutexes ([thread]) that are specially identified as synchronization operations.
These operations play a special role in making assignments in one thread visible to another.
A synchronization operation on one or more memory locations is either an acquire operation, a release operation, or both an acquire and release operation.
A synchronization operation without an associated memory location is a fence and can be either an acquire fence, a release fence, or both an acquire and release fence.
In addition, there are relaxed atomic operations, which are not synchronization operations, and atomic read-modify-write operations, which have special characteristics.
[Note 3:
For example, a call that acquires a mutex will perform an acquire operation on the locations comprising the mutex.
Correspondingly, a call that releases the same mutex will perform a release operation on those same locations.
Informally, performing a release operation onA forces priorside effects on other memory locations to become visible to other threads that later perform a consume or an acquire operation onA.
“Relaxed” atomic operations are not synchronization operations even though, like synchronization operations, they cannot contribute to data races.
— _end note_]
All modifications to a particular atomic object M occur in some particular total order, called the modification order of M.
[Note 4:
There is a separate order for each atomic object.
There is no requirement that these can be combined into a single total order for all objects.
In general this will be impossible since different threads can observe modifications to different objects in inconsistent orders.
— _end note_]
A release sequence headed by a release operation A on an atomic object Mis a maximal contiguous sub-sequence ofside effects in the modification order of M, where the first operation is A, and every subsequent operation is an atomic read-modify-write operation.
Certain library calls synchronize with other library calls performed by another thread.
For example, an atomic store-release synchronizes with a load-acquire that takes its value from the store ([atomics.order]).
[Note 5:
Except in the specified cases, reading a later value does not necessarily ensure visibility as described below.
Such a requirement would sometimes interfere with efficient implementation.
— _end note_]
[Note 6:
The specifications of the synchronization operations define when one reads the value written by another.
For atomic objects, the definition is clear.
All operations on a given mutex occur in a single total order.
Each mutex acquisition “reads the value written” by the last mutex release.
— _end note_]
An evaluation A happens before an evaluation B(or, equivalently, B happens after A) if either
- A is sequenced before B, or
- A synchronizes with B, or
- A happens before X and X happens before B.
[Note 7:
An evaluation does not happen before itself.
— _end note_]
An evaluation A strongly happens beforean evaluation D if, either
- A is sequenced before D, or
- A synchronizes with D, and both A and D are sequentially consistent atomic operations ([atomics.order]), or
- there are evaluations B and Csuch that A is sequenced before B,B happens before C, andC is sequenced before D, or
- there is an evaluation B such thatA strongly happens before B, andB strongly happens before D.
[Note 8:
Informally, if A strongly happens before B, then A appears to be evaluated before Bin all contexts.
— _end note_]
A visible side effect A on a scalar object or bit-field Mwith respect to a value computation B of M satisfies the conditions:
- A happens before B and
- there is no otherside effect X to M such that Ahappens before X and X happens before B.
The value of a non-atomic scalar object or bit-field M, as determined by evaluation B, is the value stored by thevisible side effect A.
[Note 9:
If there is ambiguity about which side effect to a non-atomic object or bit-field is visible, then the behavior is either unspecified or undefined.
— _end note_]
[Note 10:
This states that operations on ordinary objects are not visibly reordered.
This is not actually detectable without data races, but is needed to ensure that data races, as defined below, and with suitable restrictions on the use of atomics, correspond to data races in a simple interleaved (sequentially consistent) execution.
— _end note_]
The value of an atomic object M, as determined by evaluation B, is the value stored by some unspecified side effect A that modifies M, where B does not happen before A.
[Note 11:
The set of such side effects is also restricted by the rest of the rules described here, and in particular, by the coherence requirements below.
— _end note_]
If an operation A that modifies an atomic object M happens before an operation B that modifies M, then A is earlier than B in the modification order of M.
[Note 12:
This requirement is known as write-write coherence.
— _end note_]
If avalue computation A of an atomic object M happens before a value computation B of M, and A takes its value from a side effect X on M, then the value computed by B is either the value stored by X or the value stored by aside effect Y on M, where Y follows X in the modification order of M.
[Note 13:
This requirement is known as read-read coherence.
— _end note_]
If avalue computation A of an atomic object M happens before an operation B that modifies M, then A takes its value from a side effect X on M, where X precedes B in the modification order of M.
[Note 14:
This requirement is known as read-write coherence.
— _end note_]
If aside effect X on an atomic object M happens before a value computation B of M, then the evaluation B takes its value from X or from aside effect Y that follows X in the modification order of M.
[Note 15:
This requirement is known as write-read coherence.
— _end note_]
[Note 16:
The four preceding coherence requirements effectively disallow compiler reordering of atomic operations to a single object, even if both operations are relaxed loads.
This effectively makes the cache coherence guarantee provided by most hardware available to C++ atomic operations.
— _end note_]
[Note 17:
The value observed by a load of an atomic depends on the “happens before” relation, which depends on the values observed by loads of atomics.
The intended reading is that there must exist an association of atomic loads with modifications they observe that, together with suitably chosen modification orders and the “happens before” relation derived as described above, satisfy the resulting constraints as imposed here.
— _end note_]
Two actions are potentially concurrent if
- they are performed by different threads, or
- they are unsequenced, at least one is performed by a signal handler, and they are not both performed by the same signal handler invocation.
The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, at least one of which is not atomic, and neither happens before the other, except for the special case for signal handlers described below.
Any such data race results in undefined behavior.
[Note 18:
It can be shown that programs that correctly use mutexes and memory_order::seq_cst operations to prevent all data races and use no other synchronization operations behave as if the operations executed by their constituent threads were simply interleaved, with eachvalue computation of an object being taken from the lastside effect on that object in that interleaving.
This is normally referred to as “sequential consistency”.
However, this applies only to data-race-free programs, and data-race-free programs cannot observe most program transformations that do not change single-threaded program semantics.
In fact, most single-threaded program transformations remain possible, since any program that behaves differently as a result has undefined behavior.
— _end note_]
Two accesses to the same non-bit-field object of type volatile std::sig_atomic_t do not result in a data race if both occur in the same thread, even if one or more occurs in a signal handler.
For each signal handler invocation, evaluations performed by the thread invoking a signal handler can be divided into two groups A and B, such that no evaluations inB happen before evaluations in A, and the evaluations of such volatile std::sig_atomic_t objects take values as though all evaluations in A happened before the execution of the signal handler and the execution of the signal handler happened before all evaluations in B.
[Note 19:
Compiler transformations that introduce assignments to a potentially shared memory location that would not be modified by the abstract machine are generally precluded by this document, since such an assignment might overwrite another assignment by a different thread in cases in which an abstract machine execution would not have encountered a data race.
This includes implementations of data member assignment that overwrite adjacent members in separate memory locations.
Reordering of atomic loads in cases in which the atomics in question might alias is also generally precluded, since this could violate the coherence rules.
— _end note_]
[Note 20:
It is possible that transformations that introduce a speculative read of a potentially shared memory location do not preserve the semantics of the C++ program as defined in this document, since they potentially introduce a data race.
However, they are typically valid in the context of an optimizing compiler that targets a specific machine with well-defined semantics for data races.
They would be invalid for a hypothetical machine that is not tolerant of races or provides hardware race detection.
— _end note_]
6.9.2.3 Forward progress [intro.progress]
The implementation may assume that any thread will eventually do one of the following:
- terminate,
- invoke the function std::this_thread::yield ([thread.thread.this]),
- make a call to a library I/O function,
- perform an access through a volatile glvalue,
- perform a synchronization operation or an atomic operation, or
- continue execution of a trivial infinite loop ([stmt.iter.general]).
[Note 1:
This is intended to allow compiler transformations such as removal, merging, and reordering of empty loops, even when termination cannot be proven.
An affordance is made for trivial infinite loops, which cannot be removed nor reordered.
— _end note_]
Executions of atomic functions that are either defined to be lock-free ([atomics.flag]) or indicated as lock-free ([atomics.lockfree]) are lock-free executions.
- If there is only one thread that is not blocked ([defns.block]) in a standard library function, a lock-free execution in that thread shall complete.
[Note 2:
Concurrently executing threads might prevent progress of a lock-free execution.
For example, this situation can occur with load-locked store-conditional implementations.
This property is sometimes termed obstruction-free.
— _end note_] - When one or more lock-free executions run concurrently, at least one should complete.
[Note 3:
It is difficult for some implementations to provide absolute guarantees to this effect, since repeated and particularly inopportune interference from other threads could prevent forward progress, e.g., by repeatedly stealing a cache line for unrelated purposes between load-locked and store-conditional instructions.
For implementations that follow this recommendation and ensure that such effects cannot indefinitely delay progress under expected operating conditions, such anomalies can therefore safely be ignored by programmers.
Outside this document, this property is sometimes termed lock-free.
— _end note_]
During the execution of a thread of execution, each of the following is termed an execution step:
- termination of the thread of execution,
- performing an access through a volatile glvalue, or
- completion of a call to a library I/O function, a synchronization operation, or an atomic operation.
An invocation of a standard library function that blocks ([defns.block]) is considered to continuously execute execution steps while waiting for the condition that it blocks on to be satisfied.
[Example 1:
A library I/O function that blocks until the I/O operation is complete can be considered to continuously check whether the operation is complete.
Each such check consists of one or more execution steps, for example using observable behavior of the abstract machine.
— _end example_]
[Note 4:
Because of this and the preceding requirement regarding what threads of execution have to perform eventually, it follows that no thread of execution can execute forever without an execution step occurring.
— _end note_]
A thread of execution makes progresswhen an execution step occurs or a lock-free execution does not complete because there are other concurrent threads that are not blocked in a standard library function (see above).
For a thread of execution providing concurrent forward progress guarantees, the implementation ensures that the thread will eventually make progress for as long as it has not terminated.
[Note 5:
This applies regardless of whether or not other threads of execution (if any) have been or are making progress.
To eventually fulfill this requirement means that this will happen in an unspecified but finite amount of time.
— _end note_]
It is implementation-defined whether the implementation-created thread of execution that executesmain ([basic.start.main]) and the threads of execution created bystd::thread ([thread.thread.class]) or std::jthread ([thread.jthread.class]) provide concurrent forward progress guarantees.
General-purpose implementations should provide these guarantees.
For a thread of execution providing parallel forward progress guarantees, the implementation is not required to ensure that the thread will eventually make progress if it has not yet executed any execution step; once this thread has executed a step, it provides concurrent forward progress guarantees.
[Note 6:
This does not specify a requirement for when to start this thread of execution, which will typically be specified by the entity that creates this thread of execution.
For example, a thread of execution that provides concurrent forward progress guarantees and executes tasks from a set of tasks in an arbitrary order, one after the other, satisfies the requirements of parallel forward progress for these tasks.
— _end note_]
For a thread of execution providing weakly parallel forward progress guarantees, the implementation does not ensure that the thread will eventually make progress.
[Note 7:
Threads of execution providing weakly parallel forward progress guarantees cannot be expected to make progress regardless of whether other threads make progress or not; however, blocking with forward progress guarantee delegation, as defined below, can be used to ensure that such threads of execution make progress eventually.
— _end note_]
Concurrent forward progress guarantees are stronger than parallel forward progress guarantees, which in turn are stronger than weakly parallel forward progress guarantees.
[Note 8:
For example, some kinds of synchronization between threads of execution might only make progress if the respective threads of execution provide parallel forward progress guarantees, but will fail to make progress under weakly parallel guarantees.
— _end note_]
When a thread of execution P is specified toblock with forward progress guarantee delegationon the completion of a set S of threads of execution, then throughout the whole time of P being blocked on S, the implementation shall ensure that the forward progress guarantees provided by at least one thread of execution in Sis at least as strong as P's forward progress guarantees.
[Note 9:
It is unspecified which thread or threads of execution in S are chosen and for which number of execution steps.
The strengthening is not permanent and not necessarily in place for the rest of the lifetime of the affected thread of execution.
As long as P is blocked, the implementation has to eventually select and potentially strengthen a thread of execution in S.
— _end note_]
Once a thread of execution in S terminates, it is removed from S.
Once S is empty, P is unblocked.
[Note 10:
A thread of execution B thus can temporarily provide an effectively stronger forward progress guarantee for a certain amount of time, due to a second thread of execution A being blocked on it with forward progress guarantee delegation.
In turn, if B then blocks with forward progress guarantee delegation on C, this can also temporarily provide a stronger forward progress guarantee to C.
— _end note_]
[Note 11:
If all threads of execution in S finish executing (e.g., they terminate and do not use blocking synchronization incorrectly), then P's execution of the operation that blocks with forward progress guarantee delegation will not result in P's progress guarantee being effectively weakened.
— _end note_]
[Note 12:
This does not remove any constraints regarding blocking synchronization for threads of execution providing parallel or weakly parallel forward progress guarantees because the implementation is not required to strengthen a particular thread of execution whose too-weak progress guarantee is preventing overall progress.
— _end note_]
An implementation should ensure that the last value (in modification order) assigned by an atomic or synchronization operation will become visible to all other threads in a finite period of time.
6.9.3 Start and termination [basic.start]
6.9.3.1 main function [basic.start.main]
A program shall contain exactly one function called mainthat belongs to the global scope.
Executing a program starts a main thread of execution ([intro.multithread], [thread.threads]) in which the main function is invoked.
It is implementation-defined whether a program in a freestanding environment is required to define a mainfunction.
[Note 1:
In a freestanding environment, startup and termination isimplementation-defined; startup contains the execution of constructors for non-local objects with static storage duration; termination contains the execution of destructors for objects with static storage duration.
— _end note_]
An implementation shall not predefine the main function.
Its type shall have C++ language linkage and it shall have a declared return type of typeint, but otherwise its type is implementation-defined.
An implementation shall allow both
- a function of () returning int and
- a function of (int, pointer to pointer to char) returning int
as the type of main ([dcl.fct]).
In the latter form, for purposes of exposition, the first function parameter is called argc and the second function parameter is called argv, where argc shall be the number of arguments passed to the program from the environment in which the program is run.
Ifargc is nonzero these arguments shall be supplied inargv[0] through argv[argc-1] as pointers to the initial characters of null-terminated multibyte strings (ntmbss) ([multibyte.strings]) and argv[0] shall be the pointer to the initial character of an ntmbs that represents the name used to invoke the program or "".
The value of argc shall be non-negative.
The value of argv[argc] shall be 0.
Recommended practice: Any further (optional) parameters should be added after argv.
The function main shall not be named by an expression.
The linkage ([basic.link]) of main isimplementation-defined.
A program that defines main as deleted or that declares main to beinline, static, constexpr, or consteval is ill-formed.
The function main shall not be a coroutine ([dcl.fct.def.coroutine]).
The main function shall not be declared with alinkage-specification ([dcl.link]).
A program that declares
- a variable main that belongs to the global scope, or
- a function main that belongs to the global scope and is attached to a named module, or
- a function template main that belongs to the global scope, or
- an entity named mainwith C language linkage (in any namespace)
is ill-formed.
The name main is not otherwise reserved.
[Example 1:
Member functions, classes, and enumerations can be called main, as can entities in other namespaces.
— _end example_]
Terminating the program without leaving the current block (e.g., by calling the functionstd::exit(int) ([support.start.term])) does not destroy any objects with automatic storage duration ([class.dtor]).
Ifstd::exit is invoked during the destruction of an object with static or thread storage duration, the program has undefined behavior.
A return statement ([stmt.return]) in main has the effect of leaving the mainfunction (destroying any objects with automatic storage duration and evaluating any postcondition assertions of main) and calling std::exit with the return value as the argument.
If control flows off the end of the compound-statement of main, the effect is equivalent to a return with operand 0(see also [except.handle]).
6.9.3.2 Static initialization [basic.start.static]
Variables with static storage duration are initialized as a consequence of program initiation.
Variables with thread storage duration are initialized as a consequence of thread execution.
Within each of these phases of initiation, initialization occurs as follows.
Constant initialization is performed if a variable with static or thread storage duration is constant-initialized ([expr.const]).
If constant initialization is not performed, a variable with static storage duration ([basic.stc.static]) or thread storage duration ([basic.stc.thread]) is zero-initialized ([dcl.init]).
Together, zero-initialization and constant initialization are calledstatic initialization; all other initialization is dynamic initialization.
All static initialization strongly happens before ([intro.races]) any dynamic initialization.
[Note 1:
The dynamic initialization of non-block variables is described in [basic.start.dynamic]; that of static block variables is described in [stmt.dcl].
— _end note_]
An implementation is permitted to perform the initialization of a variable with static or thread storage duration as a static initialization even if such initialization is not required to be done statically, provided that
- the dynamic version of the initialization does not change the value of any other object of static or thread storage duration prior to its initialization, and
- the static version of the initialization produces the same value in the initialized variable as would be produced by the dynamic initialization if all variables not required to be initialized statically were initialized dynamically.
[Note 2:
As a consequence, if the initialization of an object obj1 refers to an object obj2 potentially requiring dynamic initialization and defined later in the same translation unit, it is unspecified whether the value of obj2 used will be the value of the fully initialized obj2 (because obj2 was statically initialized) or will be the value of obj2 merely zero-initialized.
For example,inline double fd() { return 1.0; } extern double d1;double d2 = d1; double d1 = fd();
— _end note_]
6.9.3.3 Dynamic initialization of non-block variables [basic.start.dynamic]
Dynamic initialization of a non-block variable with static storage duration is unordered if the variable is an implicitly or explicitly instantiated specialization, is partially-ordered if the variable is an inline variable that is not an implicitly or explicitly instantiated specialization, and otherwise is ordered.
[Note 1:
A non-inline explicit specialization of a templated variable has ordered initialization.
— _end note_]
A declaration D isappearance-ordered before a declaration E if
- D appears in the same translation unit as E, or
- the translation unit containing Ehas an interface dependency on the translation unit containing D,
in either case prior to E.
Dynamic initialization of non-block variables V and Wwith static storage duration are ordered as follows:
- If V and W have ordered initialization and the definition of Vis appearance-ordered before the definition of W, or if V has partially-ordered initialization,W does not have unordered initialization, and for every definition E of Wthere exists a definition D of Vsuch that D is appearance-ordered before E, then
- if the program does not start a thread ([intro.multithread]) other than the main thread ([basic.start.main]) or V and W have ordered initialization and they are defined in the same translation unit, the initialization of Vis sequenced before the initialization of W;
- otherwise, the initialization of Vstrongly happens before the initialization of W.
- Otherwise, if the program starts a thread other than the main thread before either V or W is initialized, it is unspecified in which threads the initializations of V and W occur; the initializations are unsequenced if they occur in the same thread.
- Otherwise, the initializations of V and W are indeterminately sequenced.
[Note 2:
This definition permits initialization of a sequence of ordered variables concurrently with another sequence.
— _end note_]
A non-initialization odr-useis an odr-use ([basic.def.odr]) not caused directly or indirectly by the initialization of a non-block static or thread storage duration variable.
It is implementation-defined whether the dynamic initialization of a non-block non-inline variable with static storage duration is sequenced before the first statement of main or is deferred.
If it is deferred, it strongly happens before any non-initialization odr-use of any non-inline function or non-inline variable defined in the same translation unit as the variable to be initialized.38
It is implementation-defined in which threads and at which points in the program such deferred dynamic initialization occurs.
Recommended practice: An implementation should choose such points in a way that allows the programmer to avoid deadlocks.
[Example 1: #include "a.h" #include "b.h"B b; A::A() { b.Use();} #include "a.h"A a;#include "a.h" #include "b.h" extern A a;extern B b;int main() { a.Use(); b.Use();}
It is implementation-defined whether either a or b is initialized before main is entered or whether the initializations are delayed until a is first odr-used inmain.
In particular, if a is initialized beforemain is entered, it is not guaranteed that b will be initialized before it is odr-used by the initialization of a, that is, before A::A is called.
If, however, a is initialized at some point after the first statement of main, b will be initialized prior to its use in A::A.
— _end example_]
It is implementation-defined whether the dynamic initialization of a non-block inline variable with static storage duration is sequenced before the first statement of main or is deferred.
If it is deferred, it strongly happens before any non-initialization odr-use of that variable.
It is implementation-defined in which threads and at which points in the program such deferred dynamic initialization occurs.
It is implementation-defined whether the dynamic initialization of a non-block non-inline variable with thread storage duration is sequenced before the first statement of the initial function of a thread or is deferred.
If it is deferred, the initialization associated with the entity for thread _t_is sequenced before the first non-initialization odr-use by _t_of any non-inline variable with thread storage duration defined in the same translation unit as the variable to be initialized.
It is implementation-defined in which threads and at which points in the program such deferred dynamic initialization occurs.
If the initialization of a non-block variable with static or thread storage duration exits via an exception, the function std::terminate is called ([except.terminate]).
6.9.3.4 Termination [basic.start.term]
Constructed objects ([dcl.init]) with static storage duration are destroyed and functions registered with std::atexitare called as part of a call tostd::exit ([support.start.term]).
The call to std::exit is sequenced before the destructions and the registered functions.
[Note 1:
Returning from main invokes std::exit ([basic.start.main]).
— _end note_]
Constructed objects with thread storage duration within a given thread are destroyed as a result of returning from the initial function of that thread and as a result of that thread calling std::exit.
The destruction of all constructed objects with thread storage duration within that thread strongly happens before destroying any object with static storage duration.
If the completion of the constructor or dynamic initialization of an object with static storage duration strongly happens before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first.
If the completion of the constructor or dynamic initialization of an object with thread storage duration is sequenced before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first.
If an object is initialized statically, the object is destroyed in the same order as if the object was dynamically initialized.
For an object of array or class type, all subobjects of that object are destroyed before any block variable with static storage duration initialized during the construction of the subobjects is destroyed.
If the destruction of an object with static or thread storage duration exits via an exception, the function std::terminate is called ([except.terminate]).
If a function contains a block variable of static or thread storage duration that has been destroyed and the function is called during the destruction of an object with static or thread storage duration, the program has undefined behavior if the flow of control passes through the definition of the previously destroyed block variable.
[Note 2:
Likewise, the behavior is undefined if the block variable is used indirectly (e.g., through a pointer) after its destruction.
— _end note_]
If the completion of the initialization of an object with static storage duration strongly happens before a call to std::atexit (see, [support.start.term]), the call to the function passed tostd::atexit is sequenced before the call to the destructor for the object.
If a call to std::atexit strongly happens before the completion of the initialization of an object with static storage duration, the call to the destructor for the object is sequenced before the call to the function passed to std::atexit.
If a call to std::atexit strongly happens before another call to std::atexit, the call to the function passed to the second std::atexit call is sequenced before the call to the function passed to the first std::atexit call.
If there is a use of a standard library object or function not permitted within signal handlers ([support.runtime]) that does not happen before ([intro.multithread]) completion of destruction of objects with static storage duration and execution ofstd::atexit registered functions ([support.start.term]), the program has undefined behavior.
[Note 3:
If there is a use of an object with static storage duration that does not happen before the object's destruction, the program has undefined behavior.
Terminating every thread before a call to std::exit or the exit frommain is sufficient, but not necessary, to satisfy these requirements.
These requirements permit thread managers as static-storage-duration objects.
— _end note_]
Calling the function std::abort() declared in terminates the program without executing any destructors and without calling the functions passed to std::atexit() or std::at_quick_exit().
6.10 Contract assertions [basic.contract]
6.10.1 General [basic.contract.general]
Contract assertionsallow the programmer to specify properties of the state of the program that are expected to hold at certain points during execution.
Each contract assertion has a contract-assertion predicate, which is an expression of type bool.
[Note 1:
The value of the predicate is used to identify program states that are expected.
— _end note_]
An invocation of the macro va_start ([cstdarg.syn]) shall not be a subexpression of the predicate of a contract assertion, no diagnostic required.
6.10.2 Evaluation [basic.contract.eval]
An evaluation of a contract assertion uses one of the following four evaluation semantics:ignore,observe,enforce, orquick-enforce.
Observe, enforce, and quick-enforce are checking semantics; enforce and quick-enforce are terminating semantics.
It isimplementation-defined which evaluation semantic is used for any given evaluation of a contract assertion.
[Note 1:
The range and flexibility of available choices of evaluation semantics depends on the implementation and need not allow all four evaluation semantics as possibilities.
The evaluation semantics can differ for different evaluations of the same contract assertion, including evaluations during constant evaluation.
— _end note_]
Recommended practice: An implementation should provide the option to translate a program such that all evaluations of contract assertions use the ignore semantic as well as the option to translate a program such that all evaluations of contract assertions use the enforce semantic.
By default, evaluations of contract assertions should use the enforce semantic.
The evaluation of a contract assertion using the ignore semantic has no effect.
[Note 2:
The predicate is potentially evaluated ([basic.def.odr]), but not evaluated.
— _end note_]
The evaluation A of a contract assertion using a checking semantic determines the value of the predicate.
It is unspecified whether the predicate is evaluated.
Let B be the value that would result from evaluating the predicate.
[Note 3:
To determine whether a predicate would evaluate to true or false, an alternative evaluation that produces the same value as the predicate but has no side effects can occur.
[Example 1: struct S { mutable int g = 5;} s;void f() pre(( s.g++, false )); void g() { f(); } — _end example_]
— _end note_]
There is an observable checkpoint ([intro.abstract]) Cthat happens before Asuch that any other operation Othat happens before Aalso happens before C.
A contract violation occurs when
- B is false,
- the evaluation of the predicate exits via an exception, or
- the evaluation of the predicate is performed in a context that is manifestly constant-evaluated ([expr.const]) and the predicate is not a core constant expression.
[Note 4:
If B is true, no contract violation occurs and control flow continues normally after the point of evaluation of the contract assertion.
The evaluation of the predicate can fail to produce a value without causing a contract violation, for example, by calling longjmp ([csetjmp.syn]) or terminating the program.
— _end note_]
If a contract violation occurs in a context that is manifestly constant-evaluated ([expr.const]), and the evaluation semantic is a terminating semantic, the program is ill-formed.
[Note 5:
A diagnostic is produced if the evaluation semantic is observe ([intro.compliance]).
— _end note_]
[Note 6:
Different evaluation semantics chosen for the same contract assertion in different translation units can result in violations of the one-definition rule ([basic.def.odr]) when a contract assertion has side effects that alter the value produced by a constant expression.
[Example 2: constexpr int f(int i) { contract_assert((++const_cast<int&>(i), true));return i;} inline void g() { int a[f(1)]; } — _end example_]
— _end note_]
When the program is contract-terminated, it isimplementation-defined (depending on context) whether
- std::terminate is called,
- std::abort is called, or
- execution is terminated.
[Note 7:
No further execution steps occur ([intro.progress]).
— _end note_]
[Note 8:
Performing the actions ofstd::terminate or std::abortwithout actually making a library call is a conforming implementation of contract-termination ([intro.abstract]).
— _end note_]
If a contract violation occurs in a context that is not manifestly constant-evaluated and the evaluation semantic is quick-enforce, the program is contract-terminated.
If a contract violation occurs in a context that is not manifestly constant-evaluated and the evaluation semantic is enforce or observe, the contract-violation handler ([basic.contract.handler]) is invoked with an lvalue referring to an object vof type const std::contracts::contract_violation ([support.contract.violation]) containing information about the contract violation.
Storage for vis allocated in an unspecified manner except as noted in [basic.stc.dynamic.allocation].
The lifetime of vpersists for the duration of the invocation of the contract-violation handler.
If the contract violation occurred because the evaluation of the predicate exited via an exception, the contract-violation handler is invoked from within an active implicit handler for that exception ([except.handle]).
If the contract-violation handler returns normally and the evaluation semantic is observe, that implicit handler is no longer considered active.
[Note 9:
The exception can be inspected or rethrown within the contract-violation handler.
— _end note_]
If the contract-violation handler returns normally and the evaluation semantic is enforce, the program is contract-terminated; if violation occurred as the result of an uncaught exception from the evaluation of the predicate, the implicit handler remains active when contract termination occurs.
[Note 10:
If the contract-violation handler returns normally and the evaluation semantic is observe, control flow continues normally after the point of evaluation of the contract assertion.
— _end note_]
There is an observable checkpoint ([intro.abstract]) Cthat happens after the contract-violation handler returns normally such that any other operation Othat happens after the contract-violation handler returns also happens after C.
[Note 11:
The terminating semantics terminate the program if execution would otherwise continue normally past a contract violation: the enforce semantic provides the opportunity to log information about the contract violation before terminating the program or to throw an exception to avoid termination, and the quick-enforce semantic is intended to terminate the program as soon as possible as well as to minimize the impact of contract checks on the generated code size.
Conversely, the observe semantic provides the opportunity to log information about the contract violation without having to terminate the program.
— _end note_]
If a contract-violation handler invoked from the evaluation of a function contract assertion ([dcl.contract.func]) exits via an exception, the behavior is as if the function body exits via that same exception.
[Note 12:
A function-try-block ([except.pre]) is the function body when present and thus does not have an opportunity to catch the exception.
If the function has a non-throwing exception specification, the function std::terminate is invoked ([except.terminate]).
— _end note_]
[Note 13:
If a contract-violation handler invoked from an assertion-statement ([stmt.contract.assert])) exits via an exception, the search for a handler continues from the execution of that statement.
— _end note_]
To evaluate in sequence a list R of contract assertions:
- Construct a list of contract assertions S such that
- all elements of R are in S,
- each element of Rmay be repeated animplementation-defined number of times within S, and
- if a contract assertion Aprecedes another contract assertion Bin R, then the first occurrence of Aprecedes the first occurrence of Bin S.
- Evaluate each element of S such that, if a contract assertion Aprecedes a contract assertion Bin S, then the evaluation of Ais sequenced before the evaluation of B.
[Example 3: void f(int i) { contract_assert(i > 0); contract_assert(i < 10); } — _end example_]
Recommended practice: An implementation should provide an option to perform a specified number of repeated evaluations for contract assertions.
By default, no repeated evaluations should be performed.
6.10.3 Contract-violation handler [basic.contract.handler]
The contract-violation handlerof a program is a function named::handle_contract_violation.
The contract-violation handler shall have a single parameter of type “lvalue reference to const std::contracts::contract_violation” and shall return void.
The contract-violation handler may have a non-throwing exception specification.
The implementation shall provide a definition of the contract-violation handler, called the default contract-violation handler.
[Note 1:
No declaration for the default contract-violation handler is provided by any standard library header.
— _end note_]
Recommended practice: The default contract-violation handler should produce diagnostic output that suitably formats the most relevant contents of the std::contracts::contract_violation object, rate-limited for potentially repeated violations of observed contract assertions, and then return normally.
It isimplementation-defined whether the contract-violation handler is replaceable ([dcl.fct.def.replace]).
If the contract-violation handler is not replaceable, a declaration of a replacement function for the contract-violation handler is ill-formed, no diagnostic required.