8 Declarators [dcl.decl]

A declarator declares a single variable, function, or type, within a declaration. The init-declarator-list appearing in a declaration is a comma-separated sequence of declarators, each of which can have an initializer.

init-declarator-list:
    init-declarator
    init-declarator-list , init-declarator
init-declarator:
    declarator initializeropt

The three components of a simple-declaration are the attributes ([dcl.attr]), the specifiers (decl-specifier-seq; [dcl.spec]) and the declarators (init-declarator-list). The specifiers indicate the type, storage class or other properties of the entities being declared. The declarators specify the names of these entities and (optionally) modify the type of the specifiers with operators such as * (pointer to) and () (function returning). Initial values can also be specified in a declarator; initializers are discussed in [dcl.init] and [class.init].

Each init-declarator in a declaration is analyzed separately as if it was in a declaration by itself.97

The optional attribute-specifier-seq in a trailing-return-type appertains to the indicated return type. The type-id in a trailing-return-type includes the longest possible sequence of abstract-declarators. [ Note: This resolves the ambiguous binding of array and function declarators. [ Example:

auto f()->int(*)[4];  // function returning a pointer to array[4] of int
                      // not function returning array[4] of pointer to int

 — end example ]  — end note ]

A declaration with several declarators is usually equivalent to the corresponding sequence of declarations each with a single declarator. That is

T D1, D2, ... Dn;

is usually equivalent to

T D1; T D2; ... T Dn;

where T is a decl-specifier-seq and each Di is an init-declarator. An exception occurs when a name introduced by one of the declarators hides a type name used by the decl-specifiers, so that when the same decl-specifiers are used in a subsequent declaration, they do not have the same meaning, as in

struct S ... ;
S S, T; // declare two instances of struct S

which is not equivalent to

struct S ... ;
S S;
S T; // error

Another exception occurs when T is auto ([dcl.spec.auto]), for example:

auto i = 1, j = 2.0; // error: deduced types for i and j do not match
as opposed to
auto i = 1; // OK: i deduced to have type int
auto j = 2.0; // OK: j deduced to have type double

8.1 Type names [dcl.name]

To specify type conversions explicitly, and as an argument of sizeof, alignof, new, or typeid, the name of a type shall be specified. This can be done with a type-id, which is syntactically a declaration for a variable or function of that type that omits the name of the entity.

type-id:
    type-specifier-seq abstract-declaratoropt
abstract-declarator:
    ptr-abstract-declarator
    noptr-abstract-declaratoropt parameters-and-qualifiers trailing-return-type
    abstract-pack-declarator
ptr-abstract-declarator:
    noptr-abstract-declarator
    ptr-operator ptr-abstract-declaratoropt
noptr-abstract-declarator:
    noptr-abstract-declaratoropt parameters-and-qualifiers
    noptr-abstract-declaratoropt [ constant-expressionopt ] attribute-specifier-seqopt
    ( ptr-abstract-declarator )
abstract-pack-declarator:
    noptr-abstract-pack-declarator
    ptr-operator abstract-pack-declarator
noptr-abstract-pack-declarator:
    noptr-abstract-pack-declarator parameters-and-qualifiers
    noptr-abstract-pack-declarator [ constant-expressionopt ] attribute-specifier-seqopt
    ...

It is possible to identify uniquely the location in the abstract-declarator where the identifier would appear if the construction were a declarator in a declaration. The named type is then the same as the type of the hypothetical identifier. [ Example:

int                 // int i
int *               // int *pi
int *[3]            // int *p[3]
int (*)[3]          // int (*p3i)[3]
int *()             // int *f()
int (*)(double)     // int (*pf)(double)

name respectively the types “int,” “pointer to int,” “array of 3 pointers to int,” “pointer to array of 3 int,” “function of (no parameters) returning pointer to int,” and “pointer to a function of (double) returning int.”  — end example ]

A type can also be named (often more easily) by using a typedef ([dcl.typedef]).

8.2 Ambiguity resolution [dcl.ambig.res]

The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in [stmt.ambig] can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in [stmt.ambig], the resolution is to consider any construct that could possibly be a declaration a declaration. [ Note: A declaration can be explicitly disambiguated by a nonfunction-style cast, by an = to indicate initialization or by removing the redundant parentheses around the parameter name.  — end note ] [ Example:

struct S {
  S(int);
};

void foo(double a) {
  S w(int(a));      // function declaration
  S x(int());       // function declaration
  S y((int)a);      // object declaration
  S z = int(a);     // object declaration
}

 — end example ]

The ambiguity arising from the similarity between a function-style cast and a type-id can occur in different contexts. The ambiguity appears as a choice between a function-style cast expression and a declaration of a type. The resolution is that any construct that could possibly be a type-id in its syntactic context shall be considered a type-id.

Example:

#include <cstddef>
char *p;
void *operator new(std::size_t, int);
void foo()  {
  const int x = 63;
  new (int(*p)) int;            // new-placement expression
  new (int(*[x]));              // new type-id
}

For another example,

template <class T>
struct S {
  T *p;
};
S<int()> x;                     // type-id
S<int(1)> y;                    // expression (ill-formed)

For another example,

void foo() {
  sizeof(int(1));               // expression
  sizeof(int());                // type-id (ill-formed)
}

For another example,

void foo() {
  (int(1));                     // expression
  (int())1;                     // type-id (ill-formed)
}

 — end example ]

Another ambiguity arises in a parameter-declaration-clause of a function declaration, or in a type-id that is the operand of a sizeof or typeid operator, when a type-name is nested in parentheses. In this case, the choice is between the declaration of a parameter of type pointer to function and the declaration of a parameter with redundant parentheses around the declarator-id. The resolution is to consider the type-name as a simple-type-specifier rather than a declarator-id. [ Example:

class C { };
void f(int(C)) { }              // void f(int(*fp)(C c)) { }
                                // not: void f(int C);

int g(C);

void foo() {
  f(1);                         // error: cannot convert 1 to function pointer
  f(g);                         // OK
}

For another example,

class C { };
void h(int *(C[10]));           // void h(int *(*_fp)(C _parm[10]));
                                // not: void h(int *C[10]);

 — end example ]

8.3 Meaning of declarators [dcl.meaning]

A list of declarators appears after an optional (Clause [dcl.dcl]) decl-specifier-seq ([dcl.spec]). Each declarator contains exactly one declarator-id; it names the identifier that is declared. An unqualified-id occurring in a declarator-id shall be a simple identifier except for the declaration of some special functions ([class.conv], [class.dtor], [over.oper]) and for the declaration of template specializations or partial specializations ([temp.spec]). A declarator-id shall not be qualified except for the definition of a member function ([class.mfct]) or static data member ([class.static]) outside of its class, the definition or explicit instantiation of a function or variable member of a namespace outside of its namespace, or the definition of an explicit specialization outside of its namespace, or the declaration of a friend function that is a member of another class or namespace ([class.friend]). When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers (or, in the case of a namespace, of an element of the inline namespace set of that namespace ([namespace.def])) or to a specialization thereof; the member shall not merely have been introduced by a using-declaration in the scope of the class or namespace nominated by the nested-name-specifier of the declarator-id. The nested-name-specifier of a qualified declarator-id shall not begin with a decltype-specifier. [ Note: If the qualifier is the global :: scope resolution operator, the declarator-id refers to a name declared in the global namespace scope.  — end note ] The optional attribute-specifier-seq following a declarator-id appertains to the entity that is declared.

A static, thread_local, extern, register, mutable, friend, inline, virtual, or typedef specifier applies directly to each declarator-id in an init-declarator-list; the type specified for each declarator-id depends on both the decl-specifier-seq and its declarator.

Thus, a declaration of a particular identifier has the form

T D

where T is of the form attribute-specifier-seqopt decl-specifier-seq and D is a declarator. Following is a recursive procedure for determining the type specified for the contained declarator-id by such a declaration.

First, the decl-specifier-seq determines a type. In a declaration

T D

the decl-specifier-seq T determines the type T. [ Example: in the declaration

int unsigned i;

the type specifiers int unsigned determine the type “unsigned int” ([dcl.type.simple]).  — end example ]

In a declaration attribute-specifier-seqopt T D where D is an unadorned identifier the type of this identifier is “T”.

In a declaration T D where D has the form

( D1 )

the type of the contained declarator-id is the same as that of the contained declarator-id in the declaration

T D1

Parentheses do not alter the type of the embedded declarator-id, but they can alter the binding of complex declarators.

8.3.1 Pointers [dcl.ptr]

In a declaration T D where D has the form

* attribute-specifier-seqopt cv-qualifier-seqopt D1

and the type of the identifier in the declaration T D1 is “ derived-declarator-type-list T,” then the type of the identifier of D is “ derived-declarator-type-list cv-qualifier-seq pointer to T.” The cv-qualifiers apply to the pointer and not to the object pointed to. Similarly, the optional attribute-specifier-seq ([dcl.attr.grammar]) appertains to the pointer and not to the object pointed to.

Example: the declarations

const int ci = 10, *pc = &ci, *const cpc = pc, **ppc;
int i, *p, *const cp = &i;

declare ci, a constant integer; pc, a pointer to a constant integer; cpc, a constant pointer to a constant integer; ppc, a pointer to a pointer to a constant integer; i, an integer; p, a pointer to integer; and cp, a constant pointer to integer. The value of ci, cpc, and cp cannot be changed after initialization. The value of pc can be changed, and so can the object pointed to by cp. Examples of some correct operations are

i = ci;
*cp = ci;
pc++;
pc = cpc;
pc = p;
ppc = &pc;

Examples of ill-formed operations are

ci = 1;             // error
ci++;               // error
*pc = 2;            // error
cp = &ci;           // error
cpc++;              // error
p = pc;             // error
ppc = &p;           // error

Each is unacceptable because it would either change the value of an object declared const or allow it to be changed through a cv-unqualified pointer later, for example:

*ppc = &ci;         // OK, but would make p point to ci ...
                    // ... because of previous error
*p = 5;             // clobber ci

 — end example ]

See also [expr.ass] and [dcl.init].

Note: There are no pointers to references; see [dcl.ref]. Since the address of a bit-field ([class.bit]) cannot be taken, a pointer can never point to a bit-field.  — end note ]

8.3.2 References [dcl.ref]

In a declaration T D where D has either of the forms

& attribute-specifier-seqopt D1
&& attribute-specifier-seqopt D1

and the type of the identifier in the declaration T D1 is “ derived-declarator-type-list T,” then the type of the identifier of D is “ derived-declarator-type-list reference to T.” The optional attribute-specifier-seq appertains to the reference type. Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef ([dcl.typedef]) or of a template type argument ([temp.arg]), in which case the cv-qualifiers are ignored. [ Example:

typedef int& A;
const A aref = 3;   // ill-formed; lvalue reference to non-const initialized with rvalue

The type of aref is “lvalue reference to int”, not “lvalue reference to const int”.  — end example ] Note: A reference can be thought of as a name of an object.  — end note ] A declarator that specifies the type “reference to cv void” is ill-formed.

A reference type that is declared using & is called an lvalue reference, and a reference type that is declared using && is called an rvalue reference. Lvalue references and rvalue references are distinct types. Except where explicitly noted, they are semantically equivalent and commonly referred to as references.

Example:

void f(double& a) { a += 3.14; }
// ...
double d = 0;
f(d);

declares a to be a reference parameter of f so the call f(d) will add 3.14 to d.

int v[20];
// ...
int& g(int i) { return v[i]; }
// ...
g(3) = 7;

declares the function g() to return a reference to an integer so g(3)=7 will assign 7 to the fourth element of the array v. For another example,

struct link {
  link* next;
};

link* first;

void h(link*& p) {  // p is a reference to pointer
  p->next = first;
  first = p;
  p = 0;
}

void k() {
   link* q = new link;
   h(q);
}

declares p to be a reference to a pointer to link so h(q) will leave q with the value zero. See also [dcl.init.ref].  — end example ]

It is unspecified whether or not a reference requires storage ([basic.stc]).

There shall be no references to references, no arrays of references, and no pointers to references. The declaration of a reference shall contain an initializer ([dcl.init.ref]) except when the declaration contains an explicit extern specifier ([dcl.stc]), is a class member ([class.mem]) declaration within a class definition, or is the declaration of a parameter or a return type ([dcl.fct]); see [basic.def]. A reference shall be initialized to refer to a valid object or function. [ Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior. As described in [class.bit], a reference cannot be bound directly to a bit-field.  — end note ]

If a typedef ([dcl.typedef]), a type template-parameter ([temp.arg.type]), or a decltype-specifier ([dcl.type.simple]) denotes a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR. [ Example:

int i;
typedef int& LRI;
typedef int&& RRI;

LRI& r1 = i;                    // r1 has the type int&
const LRI& r2 = i;              // r2 has the type int&
const LRI&& r3 = i;             // r3 has the type int&

RRI& r4 = i;                    // r4 has the type int&
RRI&& r5 = 5;                   // r5 has the type int&&

decltype(r2)& r6 = i;           // r6 has the type int&
decltype(r2)&& r7 = i;          // r7 has the type int&

 — end example ]

8.3.3 Pointers to members [dcl.mptr]

In a declaration T D where D has the form

nested-name-specifier * attribute-specifier-seqopt cv-qualifier-seqopt D1

and the nested-name-specifier denotes a class, and the type of the identifier in the declaration T D1 is “ derived-declarator-type-list T”, then the type of the identifier of D is “ derived-declarator-type-list cv-qualifier-seq pointer to member of class nested-name-specifier of type T”. The optional attribute-specifier-seq ([dcl.attr.grammar]) appertains to the pointer-to-member.

Example:

struct X {
  void f(int);
  int a;
};
struct Y;

int X::* pmi = &X::a;
void (X::* pmf)(int) = &X::f;
double X::* pmd;
char Y::* pmc;

declares pmi, pmf, pmd and pmc to be a pointer to a member of X of type int, a pointer to a member of X of type void(int), a pointer to a member of X of type double and a pointer to a member of Y of type char respectively. The declaration of pmd is well-formed even though X has no members of type double. Similarly, the declaration of pmc is well-formed even though Y is an incomplete type. pmi and pmf can be used like this:

X obj;
// ...
obj.*pmi = 7;       // assign 7 to an integer
                    // member of obj
(obj.*pmf)(7);      // call a function member of obj
                    // with the argument 7

 — end example ]

A pointer to member shall not point to a static member of a class ([class.static]), a member with reference type, or “cv void.”

Note: See also [expr.unary] and [expr.mptr.oper]. The type “pointer to member” is distinct from the type “pointer”, that is, a pointer to member is declared only by the pointer to member declarator syntax, and never by the pointer declarator syntax. There is no “reference-to-member” type in C++.  — end note ]

8.3.4 Arrays [dcl.array]

In a declaration T D where D has the form

D1 [ constant-expressionopt ] attribute-specifier-seqopt

and the type of the identifier in the declaration T D1 is “derived-declarator-type-list T”, then the type of the identifier of D is an array type; if the type of the identifier of D contains the auto type-specifier, the program is ill-formed. T is called the array element type; this type shall not be a reference type, the (possibly cv-qualified) type void, a function type or an abstract class type. If the constant-expression ([expr.const]) is present, it shall be an integral constant expression and its value shall be greater than zero. The constant expression specifies the bound of (number of elements in) the array. If the value of the constant expression is N, the array has N elements numbered 0 to N-1, and the type of the identifier of D is “ derived-declarator-type-list array of N T”. An object of array type contains a contiguously allocated non-empty set of N subobjects of type T. Except as noted below, if the constant expression is omitted, the type of the identifier of D is “ derived-declarator-type-list array of unknown bound of T”, an incomplete object type. The type “ derived-declarator-type-list array of N T” is a different type from the type “ derived-declarator-type-list array of unknown bound of T”, see [basic.types]. Any type of the form “ cv-qualifier-seq array of N T” is adjusted to “array of N cv-qualifier-seq T”, and similarly for “array of unknown bound of T”. The optional attribute-specifier-seq appertains to the array. [ Example:

typedef int A[5], AA[2][3];
typedef const A CA;             // type is “array of 5 const int”
typedef const AA CAA;           // type is “array of 2 array of 3 const int”

 — end example ] [ Note: An “array of N cv-qualifier-seq T” has cv-qualified type; see [basic.type.qualifier].  — end note ]

An array can be constructed from one of the fundamental types (except void), from a pointer, from a pointer to member, from a class, from an enumeration type, or from another array.

When several “array of” specifications are adjacent, a multidimensional array is created; only the first of the constant expressions that specify the bounds of the arrays may be omitted. In addition to declarations in which an incomplete object type is allowed, an array bound may be omitted in some cases in the declaration of a function parameter ([dcl.fct]). An array bound may also be omitted when the declarator is followed by an initializer ([dcl.init]). In this case the bound is calculated from the number of initial elements (say, N) supplied ([dcl.init.aggr]), and the type of the identifier of D is “array of N T.” Furthermore, if there is a preceding declaration of the entity in the same scope in which the bound was specified, an omitted array bound is taken to be the same as in that earlier declaration, and similarly for the definition of a static data member of a class.

Example:

float fa[17], *afp[17];

declares an array of float numbers and an array of pointers to float numbers. For another example,

static int x3d[3][5][7];

declares a static three-dimensional array of integers, with rank 3 × 5 × 7. In complete detail, x3d is an array of three items; each item is an array of five arrays; each of the latter arrays is an array of seven integers. Any of the expressions x3d, x3d[i], x3d[i][j], x3d[i][j][k] can reasonably appear in an expression. Finally,

extern int x[10];
struct S {
  static int y[10];
};

int x[];                      // OK: bound is 10
int S::y[];                   // OK: bound is 10

void f() {
  extern int x[];
  int i = sizeof(x);          // error: incomplete object type
}

 — end example ]

Note: conversions affecting expressions of array type are described in [conv.array]. Objects of array types cannot be modified, see [basic.lval].  — end note ]

Note: Except where it has been declared for a class ([over.sub]), the subscript operator [] is interpreted in such a way that E1[E2] is identical to *((E1)+(E2)). Because of the conversion rules that apply to +, if E1 is an array and E2 an integer, then E1[E2] refers to the E2-th member of E1. Therefore, despite its asymmetric appearance, subscripting is a commutative operation.

A consistent rule is followed for multidimensional arrays. If E is an n-dimensional array of rank i × j × … × k, then E appearing in an expression that is subject to the array-to-pointer conversion ([conv.array]) is converted to a pointer to an (n-1)-dimensional array with rank j × … × k. If the * operator, either explicitly or implicitly as a result of subscripting, is applied to this pointer, the result is the pointed-to (n-1)-dimensional array, which itself is immediately converted into a pointer.

Example: consider

int x[3][5];

Here x is a 3 × 5 array of integers. When x appears in an expression, it is converted to a pointer to (the first of three) five-membered arrays of integers. In the expression x[i] which is equivalent to *(x+i), x is first converted to a pointer as described; then x+i is converted to the type of x, which involves multiplying i by the length of the object to which the pointer points, namely five integer objects. The results are added and indirection applied to yield an array (of five integers), which in turn is converted to a pointer to the first of the integers. If there is another subscript the same argument applies again; this time the result is an integer.  — end example ]  — end note ]

Note: It follows from all this that arrays in C++ are stored row-wise (last subscript varies fastest) and that the first subscript in the declaration helps determine the amount of storage consumed by an array but plays no other part in subscript calculations.  — end note ]

8.3.5 Functions [dcl.fct]

In a declaration T D where D has the form

D1 ( parameter-declaration-clause ) cv-qualifier-seqopt
ref-qualifieropt exception-specificationopt attribute-specifier-seqopt

and the type of the contained declarator-id in the declaration T D1 is “derived-declarator-type-list T”, the type of the declarator-id in D is “ derived-declarator-type-list function of ( parameter-declaration-clause ) cv-qualifier-seqopt ref-qualifieropt returning T”. The optional attribute-specifier-seq appertains to the function type.

In a declaration T D where D has the form

D1 ( parameter-declaration-clause ) cv-qualifier-seqopt
ref-qualifieropt exception-specificationopt attribute-specifier-seqopt trailing-return-type

and the type of the contained declarator-id in the declaration T D1 is “derived-declarator-type-list T”, T shall be the single type-specifier auto. The type of the declarator-id in D is “derived-declarator-type-list function of (parameter-declaration-clause) cv-qualifier-seqopt ref-qualifieropt returning trailing-return-type”. The optional attribute-specifier-seq appertains to the function type.

The parameter-declaration-clause determines the arguments that can be specified, and their processing, when the function is called. [ Note: the parameter-declaration-clause is used to convert the arguments specified on the function call; see [expr.call].  — end note ] If the parameter-declaration-clause is empty, the function takes no arguments. The parameter list (void) is equivalent to the empty parameter list. Except for this special case, void shall not be a parameter type (though types derived from void, such as void*, can). If the parameter-declaration-clause terminates with an ellipsis or a function parameter pack ([temp.variadic]), the number of arguments shall be equal to or greater than the number of parameters that do not have a default argument and are not function parameter packs. Where syntactically correct and where “ ...” is not part of an abstract-declarator, “ , ...” is synonymous with “ ...”. [ Example: the declaration

int printf(const char*, ...);

declares a function that can be called with varying numbers and types of arguments.

printf("hello world");
printf("a=%d b=%d", a, b);

However, the first argument must be of a type that can be converted to a const char*  — end example ] [ Note: The standard header <cstdarg> contains a mechanism for accessing arguments passed using the ellipsis (see [expr.call] and [support.runtime]).  — end note ]

A single name can be used for several different functions in a single scope; this is function overloading (Clause [over]). All declarations for a function shall agree exactly in both the return type and the parameter-type-list. The type of a function is determined using the following rules. The type of each parameter (including function parameter packs) is determined from its own decl-specifier-seq and declarator. After determining the type of each parameter, any parameter of type “array of Tor “function returning T” is adjusted to be “pointer to T” or “pointer to function returning T,” respectively. After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type. The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function's parameter-type-list. [ Note: This transformation does not affect the types of the parameters. For example, int(*)(const int p, decltype(p)*) and int(*)(int, const int*) are identical types.  — end note ]

A cv-qualifier-seq or a ref-qualifier shall only be part of:

The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type. In the latter case, the cv-qualifiers are ignored. [ Note: a function type that has a cv-qualifier-seq is not a cv-qualified type; there are no cv-qualified function types.  — end note ] [ Example:

typedef void F();
struct S {
  const F f;        // OK: equivalent to: void f();
};

 — end example ] The return type, the parameter-type-list, the ref-qualifier, and the cv-qualifier-seq, but not the default arguments ([dcl.fct.default]) or the exception specification ([except.spec]), are part of the function type. [ Note: Function types are checked during the assignments and initializations of pointers to functions, references to functions, and pointers to member functions.  — end note ]

Example: the declaration

int fseek(FILE*, long, int);

declares a function taking three arguments of the specified types, and returning int ([dcl.type]).  — end example ]

If the type of a parameter includes a type of the form “pointer to array of unknown bound of T” or “reference to array of unknown bound of T,” the program is ill-formed.99 Functions shall not have a return type of type array or function, although they may have a return type of type pointer or reference to such things. There shall be no arrays of functions, although there can be arrays of pointers to functions.

Types shall not be defined in return or parameter types. The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function definition is nested within the member-specification for that class (including definitions in nested classes defined within the class).

A typedef of function type may be used to declare a function but shall not be used to define a function ([dcl.fct.def]). [ Example:

typedef void F();
F  fv;              // OK: equivalent to void fv();
F  fv { }           // ill-formed
void fv() { }       // OK: definition of fv

 — end example ] A typedef of a function type whose declarator includes a cv-qualifier-seq shall be used only to declare the function type for a non-static member function, to declare the function type to which a pointer to member refers, or to declare the top-level function type of another function typedef declaration. [ Example:

typedef int FIC(int) const;
FIC f;              // ill-formed: does not declare a member function
struct S {
  FIC f;            // OK
};
FIC S::*pm = &S::f; // OK

 — end example ]

An identifier can optionally be provided as a parameter name; if present in a function definition ([dcl.fct.def]), it names a parameter (sometimes called “formal argument”). [ Note: In particular, parameter names are also optional in function definitions and names used for a parameter in different declarations and the definition of a function need not be the same. If a parameter name is present in a function declaration that is not a definition, it cannot be used outside of its function declarator because that is the extent of its potential scope ([basic.scope.proto]).  — end note ]

Example: the declaration

int i,
    *pi,
    f(),
    *fpi(int),
    (*pif)(const char*, const char*),
    (*fpif(int))(int);

declares an integer i, a pointer pi to an integer, a function f taking no arguments and returning an integer, a function fpi taking an integer argument and returning a pointer to an integer, a pointer pif to a function which takes two pointers to constant characters and returns an integer, a function fpif taking an integer argument and returning a pointer to a function that takes an integer argument and returns an integer. It is especially useful to compare fpi and pif. The binding of *fpi(int) is *(fpi(int)), so the declaration suggests, and the same construction in an expression requires, the calling of a function fpi, and then using indirection through the (pointer) result to yield an integer. In the declarator (*pif)(const char*, const char*), the extra parentheses are necessary to indicate that indirection through a pointer to a function yields a function, which is then called.  — end example ] [ Note: Typedefs and trailing-return-types are sometimes convenient when the return type of a function is complex. For example, the function fpif above could have been declared

typedef int  IFUNC(int);
IFUNC*  fpif(int);

or

auto fpif(int)->int(*)(int)

A trailing-return-type is most useful for a type that would be more complicated to specify before the declarator-id:

template <class T, class U> auto add(T t, U u) -> decltype(t + u);

rather than

template <class T, class U> decltype((*(T*)0) + (*(U*)0)) add(T t, U u);

 — end note ]

A declarator-id or abstract-declarator containing an ellipsis shall only be used in a parameter-declaration. Such a parameter-declaration is a parameter pack ([temp.variadic]). When it is part of a parameter-declaration-clause, the parameter pack is a function parameter pack ([temp.variadic]). [ Note: Otherwise, the parameter-declaration is part of a template-parameter-list and the parameter pack is a template parameter pack; see [temp.param].  — end note ] A function parameter pack is a pack expansion ([temp.variadic]). [ Example:

template<typename... T> void f(T (* ...t)(int, int));

int add(int, int);
float subtract(int, int);

void g() {
  f(add, subtract);
}

 — end example ]

There is a syntactic ambiguity when an ellipsis occurs at the end of a parameter-declaration-clause without a preceding comma. In this case, the ellipsis is parsed as part of the abstract-declarator if the type of the parameter names a template parameter pack that has not been expanded; otherwise, it is parsed as part of the parameter-declaration-clause.100

As indicated by syntax, cv-qualifiers are a significant component in function return types.

This excludes parameters of type “ ptr-arr-seq T2” where T2 is “pointer to array of unknown bound of T” and where ptr-arr-seq means any sequence of “pointer to” and “array of” derived declarator types. This exclusion applies to the parameters of the function, and if a parameter is a pointer to function or pointer to member function then to its parameters also, etc.

One can explicitly disambiguate the parse either by introducing a comma (so the ellipsis will be parsed as part of the parameter-declaration-clause) or by introducing a name for the parameter (so the ellipsis will be parsed as part of the declarator-id).

8.3.6 Default arguments [dcl.fct.default]

If an initializer-clause is specified in a parameter-declaration this initializer-clause is used as a default argument. Default arguments will be used in calls where trailing arguments are missing.

Example: the declaration

void point(int = 3, int = 4);

declares a function that can be called with zero, one, or two arguments of type int. It can be called in any of these ways:

point(1,2);  point(1);  point();

The last two calls are equivalent to point(1,4) and point(3,4), respectively.  — end example ]

A default argument shall be specified only in the parameter-declaration-clause of a function declaration or in a template-parameter ([temp.param]); in the latter case, the initializer-clause shall be an assignment-expression. A default argument shall not be specified for a parameter pack. If it is specified in a parameter-declaration-clause, it shall not occur within a declarator or abstract-declarator of a parameter-declaration.101

For non-template functions, default arguments can be added in later declarations of a function in the same scope. Declarations in different scopes have completely distinct sets of default arguments. That is, declarations in inner scopes do not acquire default arguments from declarations in outer scopes, and vice versa. In a given function declaration, each parameter subsequent to a parameter with a default argument shall have a default argument supplied in this or a previous declaration or shall be a function parameter pack. A default argument shall not be redefined by a later declaration (not even to the same value). [ Example:

void g(int = 0, ...);           // OK, ellipsis is not a parameter so it can follow
                                // a parameter with a default argument
void f(int, int);
void f(int, int = 7);
void h() {
  f(3);                         // OK, calls f(3, 7)
  void f(int = 1, int);         // error: does not use default
                                // from surrounding scope
}
void m() {
  void f(int, int);             // has no defaults
  f(4);                         // error: wrong number of arguments
  void f(int, int = 5);         // OK
  f(4);                         // OK, calls f(4, 5);
  void f(int, int = 5);         // error: cannot redefine, even to
                                // same value
}
void n() {
  f(6);                         // OK, calls f(6, 7)
}

 — end example ] For a given inline function defined in different translation units, the accumulated sets of default arguments at the end of the translation units shall be the same; see [basic.def.odr]. If a friend declaration specifies a default argument expression, that declaration shall be a definition and shall be the only declaration of the function or function template in the translation unit.

A default argument is implicitly converted (Clause [conv]) to the parameter type. The default argument has the same semantic constraints as the initializer in a declaration of a variable of the parameter type, using the copy-initialization semantics ([dcl.init]). The names in the default argument are bound, and the semantic constraints are checked, at the point where the default argument appears. Name lookup and checking of semantic constraints for default arguments in function templates and in member functions of class templates are performed as described in [temp.inst]. [ Example: in the following code, g will be called with the value f(2):

int a = 1;
int f(int);
int g(int x = f(a));            // default argument: f(::a)

void h() {
  a = 2;
  {
  int a = 3;
  g();                          // g(f(::a))
  }
}

 — end example ] [ Note: In member function declarations, names in default arguments are looked up as described in [basic.lookup.unqual]. Access checking applies to names in default arguments as described in Clause [class.access].  — end note ]

Except for member functions of class templates, the default arguments in a member function definition that appears outside of the class definition are added to the set of default arguments provided by the member function declaration in the class definition. Default arguments for a member function of a class template shall be specified on the initial declaration of the member function within the class template. [ Example:

class C {
  void f(int i = 3);
  void g(int i, int j = 99);
};

void C::f(int i = 3) {          // error: default argument already
}                               // specified in class scope
void C::g(int i = 88, int j) {  // in this translation unit,
}                               // C::g can be called with no argument

 — end example ]

Local variables shall not be used in a default argument. [ Example:

void f() {
  int i;
  extern void g(int x = i);     //error
  // ...
}

 — end example ]

The keyword this shall not be used in a default argument of a member function. [ Example:

class A {
  void f(A* p = this) { }       // error
};

 — end example ]

Default arguments are evaluated each time the function is called. The order of evaluation of function arguments is unspecified. Consequently, parameters of a function shall not be used in a default argument, even if they are not evaluated. Parameters of a function declared before a default argument are in scope and can hide namespace and class member names. [ Example:

int a;
int f(int a, int b = a);            // error: parameter a
                                    // used as default argument
typedef int I;
int g(float I, int b = I(2));       // error: parameter I found
int h(int a, int b = sizeof(a));    // error, parameter a used
                                    // in default argument

 — end example ] Similarly, a non-static member shall not be used in a default argument, even if it is not evaluated, unless it appears as the id-expression of a class member access expression ([expr.ref]) or unless it is used to form a pointer to member ([expr.unary.op]). [ Example: the declaration of X::mem1() in the following example is ill-formed because no object is supplied for the non-static member X::a used as an initializer.

int b;
class X {
  int a;
  int mem1(int i = a);          // error: non-static member a
                                // used as default argument
  int mem2(int i = b);          // OK;  use X::b
  static int b;
};

The declaration of X::mem2() is meaningful, however, since no object is needed to access the static member X::b. Classes, objects, and members are described in Clause [class].  — end example ] A default argument is not part of the type of a function. [ Example:

int f(int = 0);

void h() {
  int j = f(1);
  int k = f();                  // OK, means f(0)
}

int (*p1)(int) = &f;
int (*p2)() = &f;               // error: type mismatch

 — end example ] When a declaration of a function is introduced by way of a using-declaration ([namespace.udecl]), any default argument information associated with the declaration is made known as well. If the function is redeclared thereafter in the namespace with additional default arguments, the additional arguments are also known at any point following the redeclaration where the using-declaration is in scope.

A virtual function call ([class.virtual]) uses the default arguments in the declaration of the virtual function determined by the static type of the pointer or reference denoting the object. An overriding function in a derived class does not acquire default arguments from the function it overrides. [ Example:

struct A {
  virtual void f(int a = 7);
};
struct B : public A {
  void f(int a);
};
void m() {
  B* pb = new B;
  A* pa = pb;
  pa->f();          // OK, calls pa->B::f(7)
  pb->f();          // error: wrong number of arguments for B::f()
}

 — end example ]

This means that default arguments cannot appear, for example, in declarations of pointers to functions, references to functions, or typedef declarations.

8.4 Function definitions [dcl.fct.def]

8.4.1 In general [dcl.fct.def.general]

Function definitions have the form

function-definition:
    attribute-specifier-seqopt decl-specifier-seqopt declarator virt-specifier-seqopt function-body
function-body:
    ctor-initializeropt compound-statement
    function-try-block
    = default ;
    = delete ;

Any informal reference to the body of a function should be interpreted as a reference to the non-terminal function-body. The optional attribute-specifier-seq in a function-definition appertains to the function. A virt-specifier-seq can be part of a function-definition only if it is a member-declaration ([class.mem]).

The declarator in a function-definition shall have the form

D1 ( parameter-declaration-clause ) cv-qualifier-seqopt   
    ref-qualifieropt exception-specificationopt attribute-specifier-seqopt trailing-return-typeopt

as described in [dcl.fct]. A function shall be defined only in namespace or class scope.

Example: a simple example of a complete function definition is

int max(int a, int b, int c) {
  int m = (a > b) ? a : b;
  return (m > c) ? m : c;
}

Here int is the decl-specifier-seq; max(int a, int b, int c) is the declarator; { /* ... */ } is the function-body.  — end example ]

A ctor-initializer is used only in a constructor; see [class.ctor] and [class.init].

A cv-qualifier-seq or a ref-qualifier (or both) can be part of a non-static member function declaration, non-static member function definition, or pointer to member function only ([dcl.fct]); see [class.this].

Note: Unused parameters need not be named. For example,

void print(int a, int) {
  std::printf("a = %d\n",a);
}

 — end note ]

In the function-body, a function-local predefined variable denotes a block-scope object of static storage duration that is implicitly defined (see [basic.scope.local]).

The function-local predefined variable __func__ is defined as if a definition of the form

static const char __func__[] = "function-name";

had been provided, where function-name is an implementation-defined string. It is unspecified whether such a variable has an address distinct from that of any other object in the program.102

Example:

struct S {
  S() : s(__func__) { }             // OK
  const char *s;
};
void f(const char * s = __func__);  // error: __func__ is undeclared

 — end example ]

Implementations are permitted to provide additional predefined variables with names that are reserved to the implementation ([global.names]). If a predefined variable is not odr-used ([basic.def.odr]), its string value need not be present in the program image.

8.4.2 Explicitly-defaulted functions [dcl.fct.def.default]

A function definition of the form:

attribute-specifier-seqopt decl-specifier-seqopt declarator  = default ;

is called an explicitly-defaulted definition. A function that is explicitly defaulted shall

  • be a special member function,

  • have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be “reference to non-const T”, where T is the name of the member function's class) as if it had been implicitly declared, and

  • not have default arguments.

An explicitly-defaulted function may be declared constexpr only if it would have been implicitly declared as constexpr, and may have an explicit exception-specification only if it is compatible ([except.spec]) with the exception-specification on the implicit declaration. If a function is explicitly defaulted on its first declaration,

  • it is implicitly considered to be constexpr if the implicit declaration would be,

  • it is implicitly considered to have the same exception-specification as if it had been implicitly declared ([except.spec]), and

  • in the case of a copy constructor, move constructor, copy assignment operator, or move assignment operator, it shall have the same parameter type as if it had been implicitly declared.

Example:

struct S {
  constexpr S() = default;                  // ill-formed: implicit S() is not constexpr
  S(int a = 0) = default;                   // ill-formed: default argument
  void operator=(const S&) = default;       // ill-formed: non-matching return type
  ~S() throw(int) = default;                // ill-formed: exception specification does not match
private:
  int i;
  S(S&);                                    // OK: private copy constructor
};
S::S(S&) = default;                         // OK: defines copy constructor

 — end example ]

Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them ([class.ctor] [class.dtor], [class.copy]), which might mean defining them as deleted. A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [ Note: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. — end note ]

Example:

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

struct nontrivial1 {
  nontrivial1();
};
nontrivial1::nontrivial1() = default;           // not first declaration

 — end example ]

8.4.3 Deleted definitions [dcl.fct.def.delete]

A function definition of the form:

attribute-specifier-seqopt decl-specifier-seqopt declarator  = delete ;

is called a deleted definition. A function with a deleted definition is also called a deleted function.

A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed. [ Note: This includes calling the function implicitly or explicitly and forming a pointer or pointer-to-member to the function. It applies even for references in expressions that are not potentially-evaluated. If a function is overloaded, it is referenced only if the function is selected by overload resolution.  — end note ]

Example: One can enforce non-default initialization and non-integral initialization with

struct onlydouble {
  onlydouble() = delete;              // OK, but redundant
  onlydouble(std::intmax_t) = delete;
  onlydouble(double);
};

 — end example ]

Example: One can prevent use of a class in certain new expressions by using deleted definitions of a user-declared operator new for that class.

struct sometype {
  void *operator new(std::size_t) = delete;
  void *operator new[](std::size_t) = delete;
};
sometype *p = new sometype;     // error, deleted class operator new
sometype *q = new sometype[3];  // error, deleted class operator new[]

 — end example ]

Example: One can make a class uncopyable, i.e. move-only, by using deleted definitions of the copy constructor and copy assignment operator, and then providing defaulted definitions of the move constructor and move assignment operator.

struct moveonly {
  moveonly() = default;
  moveonly(const moveonly&) = delete;
  moveonly(moveonly&&) = default;
  moveonly& operator=(const moveonly&) = delete;
  moveonly& operator=(moveonly&&) = default;
  ~moveonly() = default;
};
moveonly *p;
moveonly q(*p); // error, deleted copy constructor

 — end example ]

A deleted function is implicitly inline. [ Note: The one-definition rule ([basic.def.odr]) applies to deleted definitions.  — end note ] A deleted definition of a function shall be the first declaration of the function or, for an explicit specialization of a function template, the first declaration of that specialization. [ Example:

struct sometype {
  sometype();
};
sometype::sometype() = delete;      // ill-formed; not first declaration

 — end example ]

8.5 Initializers [dcl.init]

A declarator can specify an initial value for the identifier being declared. The identifier designates a variable being initialized. The process of initialization described in the remainder of [dcl.init] applies also to initializations specified by other syntactic contexts, such as the initialization of function parameters with argument expressions ([expr.call]) or the initialization of return values ([stmt.return]).

initializer:
    brace-or-equal-initializer
    ( expression-list )
brace-or-equal-initializer:
    = initializer-clause
    braced-init-list
initializer-clause:
    assignment-expression
    braced-init-list
initializer-list:
    initializer-clause ...opt
    initializer-list , initializer-clause ...opt
braced-init-list:
    { initializer-list ,opt }
    { }

Except for objects declared with the constexpr specifier, for which see [dcl.constexpr], an initializer in the definition of a variable can consist of arbitrary expressions involving literals and previously declared variables and functions, regardless of the variable's storage duration. [ Example:

int f(int);
int a = 2;
int b = f(a);
int c(b);

 — end example ]

Note: Default arguments are more restricted; see [dcl.fct.default].

The order of initialization of variables with static storage duration is described in [basic.start] and [stmt.dcl].  — end note ]

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

  • if T is a scalar type ([basic.types]), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;103

  • if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;

  • if T is a (possibly cv-qualified) union type, the object's first non-static named data member is zero-initialized and padding is initialized to zero bits;

  • if T is an array type, each element is zero-initialized;

  • if T is a reference type, no initialization is performed.

To default-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause [class]), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);

  • if T is an array type, each element is default-initialized;

  • otherwise, no initialization is performed.

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

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause [class]) with a user-provided constructor ([class.ctor]), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);

  • if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T's implicitly-declared default constructor is non-trivial, that constructor is called.

  • if T is an array type, then each element is value-initialized;

  • otherwise, the object is zero-initialized.

An object that is value-initialized is deemed to be constructed and thus subject to provisions of this International Standard applying to “constructed” objects, objects “for which the constructor has completed,” etc., even if no constructor is invoked for the object's initialization.

A program that calls for default-initialization or value-initialization of an entity of reference type is ill-formed.

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

An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.

Note: Since () is not permitted by the syntax for initializer,

X a();

is not the declaration of an object of class X, but the declaration of a function taking no argument and returning an X. The form () is permitted in certain other initialization contexts ([expr.new], [expr.type.conv], [class.base.init]).  — end note ]

If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an object with automatic or dynamic storage duration has indeterminate value. [ Note: Objects with static or thread storage duration are zero-initialized, see [basic.start.init].  — end note ]

An initializer for a static member is in the scope of the member's class. [ Example:

int a;

struct X {
  static int a;
  static int b;
};

int X::a = 1;
int X::b = a;       // X::b = X::a

 — end example ]

The form of initialization (using parentheses or =) is generally insignificant, but does matter when the initializer or the entity being initialized has a class type; see below. If the entity being initialized does not have class type, the expression-list in a parenthesized initializer shall be a single expression.

The initialization that occurs in the form

T x = a;

as well as in argument passing, function return, throwing an exception ([except.throw]), handling an exception ([except.handle]), and aggregate member initialization ([dcl.init.aggr]) is called copy-initialization. [ Note: Copy-initialization may invoke a move ([class.copy]).  — end note ]

The initialization that occurs in the forms

T x(a);
T x{a};

as well as in new expressions ([expr.new]), static_cast expressions ([expr.static.cast]), functional notation type conversions ([expr.type.conv]), and base and member initializers ([class.base.init]) is called direct-initialization.

The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined.

  • If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized ([dcl.init.list]).

  • If the destination type is a reference type, see [dcl.init.ref].

  • If the destination type is an array of characters, an array of char16_t, an array of char32_t, or an array of wchar_t, and the initializer is a string literal, see [dcl.init.string].

  • If the initializer is (), the object is value-initialized.

  • Otherwise, if the destination type is an array, the program is ill-formed.

  • If the destination type is a (possibly cv-qualified) class type:

    • If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.

    • Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in [over.match.copy], and the best one is chosen through overload resolution ([over.match]). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is a prvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see [class.temporary], [class.copy].

  • Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated ([over.match.conv]), and the best one is chosen through overload resolution ([over.match]). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. If the conversion cannot be done or is ambiguous, the initialization is ill-formed.

  • Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. Standard conversions (Clause [conv]) will be used, if necessary, to convert the initializer expression to the cv-unqualified version of the destination type; no user-defined conversions are considered. If the conversion cannot be done, the initialization is ill-formed. Note: An expression of type “ cv1 T” can initialize an object of type “ cv2 T” independently of the cv-qualifiers cv1 and cv2.

    int a;
    const int b = a;
    int c = b;
    

     — end note ]

An initializer-clause followed by an ellipsis is a pack expansion ([temp.variadic]).

As specified in [conv.ptr], converting an integral constant expression whose value is 0 to a pointer type results in a null pointer value.

8.5.1 Aggregates [dcl.init.aggr]

An aggregate is an array or a class (Clause [class]) with no user-provided constructors ([class.ctor]), no brace-or-equal-initializers for non-static data members ([class.mem]), no private or protected non-static data members (Clause [class.access]), no base classes (Clause [class.derived]), and no virtual functions ([class.virtual]).

When an aggregate is initialized by an initializer list, as specified in [dcl.init.list], the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause. If the initializer-clause is an expression and a narrowing conversion ([dcl.init.list]) is required to convert the expression, the program is ill-formed. [ Note: If an initializer-clause is itself an initializer list, the member is list-initialized, which will result in a recursive application of the rules in this section if the member is an aggregate.  — end note ] [ Example:

struct A {
  int x;
  struct B {
    int i;
    int j;
  } b;
} a = { 1, { 2, 3 } };

initializes a.x with 1, a.b.i with 2, a.b.j with 3.  — end example ]

An aggregate that is a class can also be initialized with a single expression not enclosed in braces, as described in [dcl.init].

An array of unknown size initialized with a brace-enclosed initializer-list containing n initializer-clauses, where n shall be greater than zero, is defined as having n elements ([dcl.array]). [ Example:

int x[] = { 1, 3, 5 };

declares and initializes x as a one-dimensional array that has three elements since no size was specified and there are three initializers.  — end example ] An empty initializer list {} shall not be used as the initializer-clause for an array of unknown bound.104

Static data members and anonymous bit-fields are not considered members of the class for purposes of aggregate initialization. [ Example:

struct A {
  int i;
  static int s;
  int j;
  int :17;
  int k;
} a = { 1, 2, 3 };

Here, the second initializer 2 initializes a.j and not the static data member A::s, and the third initializer 3 initializes a.k and not the anonymous bit-field before it.  — end example ]

An initializer-list is ill-formed if the number of initializer-clauses exceeds the number of members or elements to initialize. [ Example:

char cv[4] = { 'a', 's', 'd', 'f', 0 };     // error

is ill-formed.  — end example ]

If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from an empty initializer list ([dcl.init.list]). [ Example:

struct S { int a; const char* b; int c; };
S ss = { 1, "asdf" };

initializes ss.a with 1, ss.b with "asdf", and ss.c with the value of an expression of the form int(), that is, 0.  — end example ]

If an aggregate class C contains a subaggregate member m that has no members for purposes of aggregate initialization, the initializer-clause for m shall not be omitted from an initializer-list for an object of type C unless the initializer-clauses for all members of C following m are also omitted. [ Example:

struct S { } s;
struct A {
  S s1;
  int i1;
  S s2;
  int i2;
  S s3;
  int i3;
} a = {
  { },      // Required initialization
  0,
  s,        // Required initialization
  0
};          // Initialization not required for A::s3 because A::i3 is also not initialized

 — end example ]

If an incomplete or empty initializer-list leaves a member of reference type uninitialized, the program is ill-formed.

When initializing a multi-dimensional array, the initializer-clauses initialize the elements with the last (rightmost) index of the array varying the fastest ([dcl.array]). [ Example:

int x[2][2] = { 3, 1, 4, 2 };

initializes x[0][0] to 3, x[0][1] to 1, x[1][0] to 4, and x[1][1] to 2. On the other hand,

float y[4][3] = {
  { 1 }, { 2 }, { 3 }, { 4 }
};

initializes the first column of y (regarded as a two-dimensional array) and leaves the rest zero.  — end example ]

In a declaration of the form

T x = { a };

braces can be elided in an initializer-list as follows.105 If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializer-clauses initializes the members of a subaggregate; it is erroneous for there to be more initializer-clauses than members. If, however, the initializer-list for a subaggregate does not begin with a left brace, then only enough initializer-clauses from the list are taken to initialize the members of the subaggregate; any remaining initializer-clauses are left to initialize the next member of the aggregate of which the current subaggregate is a member. [ Example:

float y[4][3] = {
  { 1, 3, 5 },
  { 2, 4, 6 },
  { 3, 5, 7 },
};

is a completely-braced initialization: 1, 3, and 5 initialize the first row of the array y[0], namely y[0][0], y[0][1], and y[0][2]. Likewise the next two lines initialize y[1] and y[2]. The initializer ends early and therefore y[3]s elements are initialized as if explicitly initialized with an expression of the form float(), that is, are initialized with 0.0. In the following example, braces in the initializer-list are elided; however the initializer-list has the same effect as the completely-braced initializer-list of the above example,

float y[4][3] = {
  1, 3, 5, 2, 4, 6, 3, 5, 7
};

The initializer for y begins with a left brace, but the one for y[0] does not, therefore three elements from the list are used. Likewise the next three are taken successively for y[1] and y[2].  — end example ]

All implicit type conversions (Clause [conv]) are considered when initializing the aggregate member with an assignment-expression. If the assignment-expression can initialize a member, the member is initialized. Otherwise, if the member is itself a subaggregate, brace elision is assumed and the assignment-expression is considered for the initialization of the first member of the subaggregate. [ Note: As specified above, brace elision cannot apply to subaggregates with no members for purposes of aggregate initialization; an initializer-clause for the entire subobject is required. — end note ]

Example:

struct A {
  int i;
  operator int();
};
struct B {
  A a1, a2;
  int z;
};
A a;
B b = { 4, a, a };

Braces are elided around the initializer-clause for b.a1.i. b.a1.i is initialized with 4, b.a2 is initialized with a, b.z is initialized with whatever a.operator int() returns.  — end example ]

Note: An aggregate array or an aggregate class may contain members of a class type with a user-provided constructor ([class.ctor]). Initialization of these aggregate objects is described in [class.expl.init].  — end note ]

Note: Whether the initialization of aggregates with static storage duration is static or dynamic is specified in [basic.start.init] and [stmt.dcl].  — end note ]

When a union is initialized with a brace-enclosed initializer, the braces shall only contain an initializer-clause for the first non-static data member of the union. [ Example:

union u { int a; const char* b; };
u a = { 1 };
u b = a;
u c = 1;                        // error
u d = { 0, "asdf" };            // error
u e = { "asdf" };               // error

 — end example ]

Note: As described above, the braces around the initializer-clause for a union member can be omitted if the union is a member of another aggregate.  — end note ]

The syntax provides for empty initializer-lists, but nonetheless C++ does not have zero length arrays.

Braces cannot be elided in other uses of list-initialization.

8.5.2 Character arrays [dcl.init.string]

A char array (whether plain char, signed char, or unsigned char), char16_t array, char32_t array, or wchar_t array can be initialized by a narrow character literal, char16_t string literal, char32_t string literal, or wide string literal, respectively, or by an appropriately-typed string literal enclosed in braces. Successive characters of the value of the string literal initialize the elements of the array. [ Example:

char msg[] = "Syntax error on line %s\n";

shows a character array whose members are initialized with a string-literal. Note that because '\n' is a single character and because a trailing '\0' is appended, sizeof(msg) is 25.  — end example ]

There shall not be more initializers than there are array elements. [ Example:

char cv[4] = "asdf";            // error

is ill-formed since there is no space for the implied trailing '\0'.  — end example ]

If there are fewer initializers than there are array elements, each element not explicitly initialized shall be zero-initialized ([dcl.init]).

8.5.3 References [dcl.init.ref]

A variable declared to be a T& or T&&, that is, “reference to type T” ([dcl.ref]), shall be initialized by an object, or function, of type T or by an object that can be converted into a T. [ Example:

int g(int);
void f() {
  int i;
  int& r = i;                   // r refers to i
  r = 1;                        // the value of i becomes 1
  int* p = &r;                  // p points to i
  int& rr = r;                  // rr refers to what r refers to, that is, to i
  int (&rg)(int) = g;           // rg refers to the function g
  rg(i);                        // calls function g
  int a[3];
  int (&ra)[3] = a;             // ra refers to the array a
  ra[1] = i;                    // modifies a[1]
}

 — end example ]

A reference cannot be changed to refer to another object after initialization. Note that initialization of a reference is treated very differently from assignment to it. Argument passing ([expr.call]) and function value return ([stmt.return]) are initializations.

The initializer can be omitted for a reference only in a parameter declaration ([dcl.fct]), in the declaration of a function return type, in the declaration of a class member within its class definition ([class.mem]), and where the extern specifier is explicitly used. Example:

int& r1;                        // error: initializer missing
extern int& r2;                 // OK

 — end example ]

Given types “ cv1 T1” and “ cv2 T2,” “ cv1 T1” is reference-related to cv2 T2” if T1 is the same type as T2, or T1 is a base class of T2. “ cv1 T1” is reference-compatible with “ cv2 T2” if T1 is reference-related to T2 and cv1 is the same cv-qualification as, or greater cv-qualification than, cv2. For purposes of overload resolution, cases for which cv1 is greater cv-qualification than cv2 are identified as reference-compatible with added qualification (see [over.ics.rank]). In all cases where the reference-related or reference-compatible relationship of two types is used to establish the validity of a reference binding, and T1 is a base class of T2, a program that necessitates such a binding is ill-formed if T1 is an inaccessible (Clause [class.access]) or ambiguous ([class.member.lookup]) base class of T2.

A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:

  • If the reference is an lvalue reference and the initializer expression

    • is an lvalue (but is not a bit-field), and “ cv1 T1” is reference-compatible with “ cv2 T2,” or

    • has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “ cv3 T3,” where “ cv1 T1” is reference-compatible with “ cv3 T3106 (this conversion is selected by enumerating the applicable conversion functions ([over.match.ref]) and choosing the best one through overload resolution ([over.match])),

    then the reference is bound to the initializer expression lvalue in the first case and to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object). [ Note: The usual lvalue-to-rvalue ([conv.lval]), array-to-pointer ([conv.array]), and function-to-pointer ([conv.func]) standard conversions are not needed, and therefore are suppressed, when such direct bindings to lvalues are done.  — end note ]

    Example:

    double d = 2.0;
    double& rd = d;                 // rd refers to d
    const double& rcd = d;          // rcd refers to d
    
    struct A { };
    struct B : A { operator int&(); } b;
    A& ra = b;                      // ra refers to A subobject in b
    const A& rca = b;               // rca refers to A subobject in b
    int& ir = B();                  // ir refers to the result of B::operator int&
    

     — end example ]

  • Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference. [ Example:

    double& rd2 = 2.0;              // error: not an lvalue and reference not const
    int  i = 2;
    double& rd3 = i;                // error: type mismatch and reference not const
    

     — end example ]

    • If the initializer expression

      • is an xvalue, class prvalue, array prvalue or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or

      • has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an xvalue, class prvalue, or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3”,

      then the reference is bound to the value of the initializer expression in the first case and to the result of the conversion in the second case (or, in either case, to an appropriate base class subobject). In the second case, if the reference is an rvalue reference and the second standard conversion sequence of the user-defined conversion sequence includes an lvalue-to-rvalue conversion, the program is ill-formed.

      Example:

      struct A { };
      struct B : A { } b;
      extern B f();
      const A& rca2 = f();                // bound to the A subobject of the B rvalue.
      A&& rra = f();                      // same as above
      struct X {
        operator B();
        operator int&();
      } x;
      const A& r = x;                     // bound to the A subobject of the result of the conversion
      int i2 = 42;
      int&& rri = static_cast<int&&>(i2); // bound directly to i2
      B&& rrb = x;                        // bound directly to the result of operator B
      int&& rri2 = X();                   // error: lvalue-to-rvalue conversion applied to the
                                          // result of operator int&
      

       — end example ]

    • Otherwise, a temporary of type “ cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy-initialization ([dcl.init]). The reference is then bound to the temporary. If T1 is reference-related to T2, cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2. If T1 is reference-related to T2 and the reference is an rvalue reference, the initializer expression shall not be an lvalue. [ Example:

      const double& rcd2 = 2;         // rcd2 refers to temporary with value 2.0
      double&& rrd = 2;               // rrd refers to temporary with value 2.0
      const volatile int cvi = 1;
      const int& r2 = cvi;            // error: type qualifiers dropped
      double d2 = 1.0;
      double&& rrd2 = d2;             // error: copying lvalue of related type
      int i3 = 2;
      double&& rrd3 = i3;             // rrd3 refers to temporary with value 2.0
      

       — end example ]

In all cases except the last (i.e., creating and initializing a temporary from the initializer expression), the reference is said to bind directly to the initializer expression.

Note: [class.temporary] describes the lifetime of temporaries bound to references.  — end note ]

This requires a conversion function ([class.conv.fct]) returning a reference type.

8.5.4 List-initialization [dcl.init.list]

List-initialization is initialization of an object or reference from a braced-init-list. Such an initializer is called an initializer list, and the comma-separated initializer-clauses of the list are called the elements of the initializer list. An initializer list may be empty. List-initialization can occur in direct-initialization or copy-initialization contexts; list-initialization in a direct-initialization context is called direct-list-initialization and list-initialization in a copy-initialization context is called copy-list-initialization. [ Note: List-initialization can be used

Example:

int a = {1};
std::complex<double> z{1,2};
new std::vector<std::string>{"once", "upon", "a", "time"};  // 4 string elements
f( {"Nicholas","Annemarie"} );  // pass list of two elements
return { "Norah" };             // return list of one element
int* e {};                      // initialization to zero / null pointer
x = double{1};                  // explicitly construct a double 
std::map<std::string,int> anim = { {"bear",4}, {"cassowary",2}, {"tiger",7} };

 — end example ]  — end note ]

A constructor is an initializer-list constructor if its first parameter is of type std::initializer_list<E> or reference to possibly cv-qualified std::initializer_list<E> for some type E, and either there are no other parameters or else all other parameters have default arguments ([dcl.fct.default]). [ Note: Initializer-list constructors are favored over other constructors in list-initialization ([over.match.list]). — end note ] The template std::initializer_list is not predefined; if the header <initializer_list> is not included prior to a use of std::initializer_list — even an implicit use in which the type is not named ([dcl.spec.auto]) — the program is ill-formed.

List-initialization of an object or reference of type T is defined as follows:

  • If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.

  • Otherwise, if T is an aggregate, aggregate initialization is performed ([dcl.init.aggr]).

    Example:

    double ad[] = { 1, 2.0 };           // OK
    int ai[] = { 1, 2.0 };              // error: narrowing
    
    struct S2 {
      int m1;
      double m2, m3;
    };
    S2 s21 = { 1, 2, 3.0 };             // OK
    S2 s22 { 1.0, 2, 3 };               // error: narrowing
    S2 s23 { };                         // OK: default to 0,0,0
    

     — end example ]

  • Otherwise, if T is a specialization of std::initializer_list<E>, an initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type ([dcl.init]).

  • Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution ([over.match], [over.match.list]). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.

    Example:

    struct S {
      S(std::initializer_list<double>); // #1
      S(std::initializer_list<int>);    // #2
      S();                              // #3
      // ...
    };
    S s1 = { 1.0, 2.0, 3.0 };           // invoke #1
    S s2 = { 1, 2, 3 };                 // invoke #2
    S s3 = { };                         // invoke #3
    

     — end example ]

    Example:

    struct Map {
      Map(std::initializer_list<std::pair<std::string,int>>);
    };
    Map ship = {{"Sophie",14}, {"Surprise",28}};
    

     — end example ]

    Example:

    struct S {
      // no initializer-list constructors
      S(int, double, double);           // #1
      S();                              // #2
      // ...
    };
    S s1 = { 1, 2, 3.0 };               // OK: invoke #1
    S s2 { 1.0, 2, 3 };                 // error: narrowing
    S s3 { };                           // OK: invoke #2
    

     — end example ]

  • Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary. [ Note: As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type.  — end note ]

    Example:

    struct S {
      S(std::initializer_list<double>); // #1
      S(const std::string&);            // #2
      // ...
    };
    const S& r1 = { 1, 2, 3.0 };        // OK: invoke #1
    const S& r2 { "Spinach" };          // OK: invoke #2
    S& r3 = { 1, 2, 3 };                // error: initializer is not an lvalue
    const int& i1 = { 1 };              // OK
    const int& i2 = { 1.1 };            // error: narrowing
    const int (&iar)[2] = { 1, 2 };     // OK: iar is bound to temporary array
    

     — end example ]

  • Otherwise, if the initializer list has a single element, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.

    Example:

    int x1 {2};                         // OK
    int x2 {2.0};                       // error: narrowing
    

     — end example ]

  • Otherwise, if the initializer list has no elements, the object is value-initialized.

    Example:

    int** pp {};                        // initialized to null pointer
    

     — end example ]

  • Otherwise, the program is ill-formed.

    Example:

    struct A { int i; int j; };
    A a1 { 1, 2 };                      // aggregate initialization 
    A a2 { 1.2 };                       // error: narrowing
    struct B {
      B(std::initializer_list<int>);
    };
    B b1 { 1, 2 };                      // creates initializer_list<int> and calls constructor
    B b2 { 1, 2.0 };                    // error: narrowing
    struct C {
      C(int i, double j);
    };
    C c1 = { 1, 2.2 };                  // calls constructor with arguments (1, 2.2) 
    C c2 = { 1.1, 2 };                  // error: narrowing
    
    int j { 1 };                        // initialize to 1
    int k { };                          // initialize to 0
    

     — end example ]

Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions ([temp.variadic]), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list. [ Note: This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call.  — end note ]

An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated an array of N elements of type E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array. If a narrowing conversion is required to initialize any of the elements, the program is ill-formed.[ Example:

struct X {
  X(std::initializer_list<double> v);
};
X x{ 1,2,3 };

The initialization will be implemented in a way roughly equivalent to this:

double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));

assuming that the implementation can construct an initializer_list object with a pair of pointers.  — end example ]

The lifetime of the array is the same as that of the initializer_list object. [ Example:

typedef std::complex<double> cmplx;
std::vector<cmplx> v1 = { 1, 2, 3 };

void f() {
  std::vector<cmplx> v2{ 1, 2, 3 };
  std::initializer_list<int> i3 = { 1, 2, 3 };
}

For v1 and v2, the initializer_list object and array created for { 1, 2, 3 } have full-expression lifetime. For i3, the initializer_list object and array have automatic lifetime.  — end example ] [ Note: The implementation is free to allocate the array in read-only memory if an explicit array with the same initializer could be so allocated.  — end note ]

A narrowing conversion is an implicit conversion

  • from a floating-point type to an integer type, or

  • from long double to double or float, or from double to float, except where the source is a constant expression and the actual value after conversion is within the range of values that can be represented (even if it cannot be represented exactly), or

  • from an integer type or unscoped enumeration type to a floating-point type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type, or

  • from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type.

Note: As indicated above, such conversions are not allowed at the top level in list-initializations. — end note ] [ Example:

int x = 999;              // x is not a constant expression
const int y = 999;
const int z = 99;
char c1 = x;              // OK, though it might narrow (in this case, it does narrow)
char c2{x};               // error: might narrow
char c3{y};               // error: narrows (assuming char is 8 bits)
char c4{z};               // OK: no narrowing needed
unsigned char uc1 = {5};  // OK: no narrowing needed
unsigned char uc2 = {-1}; // error: narrows
unsigned int ui1 = {-1};  // error: narrows
signed int si1 =
  { (unsigned int)-1 };   // error: narrows
int ii = {2.0};           // error: narrows
float f1 { x };           // error: might narrow
float f2 { 7 };           // OK: 7 can be exactly represented as a float
int f(int);
int a[] =
  { 2, f(2), f(2.0) };    // OK: the double-to-int conversion is not at the top level

 — end example ]