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.
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 is 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
declarator: ptr-declarator noptr-declarator parameters-and-qualifiers trailing-return-type
ptr-declarator: noptr-declarator ptr-operator ptr-declarator
noptr-declarator: declarator-id attribute-specifier-seq noptr-declarator parameters-and-qualifiers noptr-declarator [ constant-expression ] attribute-specifier-seq ( ptr-declarator )
parameters-and-qualifiers: ( parameter-declaration-clause ) cv-qualifier-seq ref-qualifier noexcept-specifier attribute-specifier-seq
trailing-return-type: -> type-id
ptr-operator: * attribute-specifier-seq cv-qualifier-seq & attribute-specifier-seq && attribute-specifier-seq nested-name-specifier * attribute-specifier-seq cv-qualifier-seq
cv-qualifier-seq: cv-qualifier cv-qualifier-seq
cv-qualifier: const volatile
ref-qualifier: & &&
declarator-id: ... id-expression
type-id: type-specifier-seq abstract-declarator
defining-type-id: defining-type-specifier-seq abstract-declarator
abstract-declarator: ptr-abstract-declarator noptr-abstract-declarator parameters-and-qualifiers trailing-return-type abstract-pack-declarator
ptr-abstract-declarator: noptr-abstract-declarator ptr-operator ptr-abstract-declarator
noptr-abstract-declarator: noptr-abstract-declarator parameters-and-qualifiers noptr-abstract-declarator [ constant-expression ] attribute-specifier-seq ( 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-expression ] attribute-specifier-seq ...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.
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”.
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 y((int)a); // object declaration S z = int(a); // object declaration }
template <class T> struct X {}; template <int N> struct Y {}; X<int()> a; // type-id X<int(1)> b; // expression (ill-formed) Y<int()> c; // type-id (ill-formed) Y<int(1)> d; // expression void foo(signed char a) { sizeof(int()); // type-id (ill-formed) sizeof(int(a)); // expression sizeof(int(unsigned(a))); // type-id (ill-formed) (int())+1; // type-id (ill-formed) (int(a))+1; // expression (int(unsigned(a)))+1; // type-id (ill-formed) }
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 }
class C { }; void h(int *(C[10])); // void h(int *(*_fp)(C _parm[10])); // not: void h(int *C[10]);
T D
T D
( D1 )the type of the contained declarator-id is the same as that of the contained declarator-id in the declaration
T D1Parentheses do not alter the type of the embedded declarator-id, but they can alter the binding of complex declarators.
* attribute-specifier-seq cv-qualifier-seq D1and 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”.
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.
i = ci; *cp = ci; pc++; pc = cpc; pc = p; ppc = &pc;
ci = 1; // error ci++; // error *pc = 2; // error cp = &ci; // error cpc++; // error p = pc; // error ppc = &p; // errorEach 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
& attribute-specifier-seq D1 && attribute-specifier-seq D1and 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”.
void f(double& a) { a += 3.14; }
// ...
double d = 0;
f(d);
int v[20]; // ... int& g(int i) { return v[i]; } // ... g(3) = 7;
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);
}
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&
nested-name-specifier * attribute-specifier-seq cv-qualifier-seq D1and 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”.
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.
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
D1 [ constant-expression ] attribute-specifier-seqand 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.
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”
float fa[17], *afp[17];declares an array of float numbers and an array of pointers to float numbers.
static int x3d[3][5][7];
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 }
int x[3][5];
D1 ( parameter-declaration-clause ) cv-qualifier-seq ref-qualifier noexcept-specifier attribute-specifier-seqand 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 noexcept function of (parameter-declaration-clause) cv-qualifier-seq ref-qualifier returning T”, where the optional noexcept is present if and only if the exception specification ([except.spec]) is non-throwing.
D1 ( parameter-declaration-clause ) cv-qualifier-seq ref-qualifier noexcept-specifier attribute-specifier-seq trailing-return-typeand 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.
parameter-declaration-clause: parameter-declaration-list ... parameter-declaration-list , ...
parameter-declaration-list: parameter-declaration parameter-declaration-list , parameter-declaration
parameter-declaration: attribute-specifier-seq decl-specifier-seq declarator attribute-specifier-seq decl-specifier-seq declarator = initializer-clause attribute-specifier-seq decl-specifier-seq abstract-declarator attribute-specifier-seq decl-specifier-seq abstract-declarator = initializer-clauseThe optional attribute-specifier-seq in a parameter-declaration appertains to the parameter.
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
typedef void F();
struct S {
const F f; // OK: equivalent to: void f();
};
typedef void F(); F fv; // OK: equivalent to void fv(); F fv { } // ill-formed void fv() { } // OK: definition of fv
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.
typedef int IFUNC(int); IFUNC* fpif(int);
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);
template <class T, class U> decltype((*(T*)0) + (*(U*)0)) add(T t, U u);
template<typename... T> void f(T (* ...t)(int, int)); int add(int, int); float subtract(int, int); void g() { f(add, subtract); }
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) }
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)) } }
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
void f() { int i; extern void g(int x = i); // error extern void h(int x = sizeof(i)); // OK // ... }
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)); // OK, unevaluated operand
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; };
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
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() }
function-definition: attribute-specifier-seq decl-specifier-seq declarator virt-specifier-seq function-body
function-body: ctor-initializer 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.
static const char __func__[] = "function-name";
had been provided, where function-name is an implementation-defined string.struct S { S() : s(__func__) { } // OK const char* s; }; void f(const char* s = __func__); // error: __func__ is undeclared
attribute-specifier-seq decl-specifier-seq declarator virt-specifier-seq = default ;is called an explicitly-defaulted definition.
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() noexcept(false) = default; // deleted: exception specification does not match private: int i; S(S&); // OK: private copy constructor }; S::S(S&) = default; // OK: defines copy constructor
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
attribute-specifier-seq decl-specifier-seq declarator virt-specifier-seq = delete ;is called a deleted definition.
struct onlydouble {
onlydouble() = delete; // OK, but redundant
onlydouble(std::intmax_t) = delete;
onlydouble(double);
};
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[]
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
struct sometype {
sometype();
};
sometype::sometype() = delete; // ill-formed; not first declaration
attribute-specifier-seq decl-specifier-seq ref-qualifier e initializer ;where the declaration is never interpreted as a function declaration and the parts of the declaration other than the declarator-id are taken from the corresponding structured binding declaration.
auto f() -> int(&)[2]; auto [ x, y ] = f(); // x and y refer to elements in a copy of the array return value auto& [ xr, yr ] = f(); // xr and yr refer to elements in the array referred to by f's return value
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 ... initializer-list , initializer-clause ...
braced-init-list: { initializer-list , } { }
expr-or-braced-init-list: expression braced-init-list
X a();
int f(bool b) { unsigned char c; unsigned char d = c; // OK, d has an indeterminate value int e = d; // undefined behavior return b ? d : 0; // undefined behavior if b is true }
int a;
struct X {
static int a;
static int b;
};
int X::a = 1;
int X::b = a; // X::b = X::a
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]), mem-initializers, and the braced-init-list form of a condition is called direct-initialization.
struct A { int x; struct B { int i; int j; } b; } a = { 1, { 2, 3 } };
struct base1 { int b1, b2 = 42; }; struct base2 { base2() { b3 = 42; } int b3; }; struct derived : base1, base2 { int d; }; derived d1{{1, 2}, {}, 4}; derived d2{{}, {}, 4};
struct S {
int y[] = { 0 }; // error: non-static data member of incomplete type
};
struct S { int a; const char* b; int c; int d = b[a]; }; S ss = { 1, "asdf" };
struct X { int i, j, k = 42; }; X a[] = { 1, 2, 3, 4, 5, 6 }; X b[2] = { { 1, 2, 3 }, { 4, 5, 6 } };
struct A; extern A a; struct A { const A& a1 { A{a,a} }; // OK const A& a2 { A{} }; // error }; A a{a,a}; // OK
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
float y[4][3] = { { 1, 3, 5 }, { 2, 4, 6 }, { 3, 5, 7 }, };
float y[4][3] = { 1, 3, 5, 2, 4, 6, 3, 5, 7 };
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
int g(int) noexcept; 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] }
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&
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
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
struct Banana { }; struct Enigma { operator const Banana(); }; struct Alaska { operator Banana&(); }; void enigmatic() { typedef const Banana ConstBanana; Banana &&banana1 = ConstBanana(); // ill-formed Banana &&banana2 = Enigma(); // ill-formed Banana &&banana3 = Alaska(); // ill-formed } 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: cv-qualifier dropped struct A { operator volatile int&(); } a; const int& r3 = a; // error: cv-qualifier dropped // from result of conversion function double d2 = 1.0; double&& rrd2 = d2; // error: initializer is lvalue of related type struct X { operator int&(); }; int&& rri2 = X(); // error: result of conversion function is lvalue of related type int i3 = 2; double&& rrd3 = i3; // rrd3 refers to temporary with value 2.0
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} };
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
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
struct Map { Map(std::initializer_list<std::pair<std::string,int>>); }; Map ship = {{"Sophie",14}, {"Surprise",28}};
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
enum byte : unsigned char { }; byte b { 42 }; // OK byte c = { 42 }; // error byte d = byte{ 42 }; // OK; same value as b byte e { -1 }; // error struct A { byte b; }; A a1 = { { 42 } }; // error A a2 = { byte{ 42 } }; // OK void f(byte); f({ 42 }); // error enum class Handle : uint32_t { Invalid = 0 }; Handle h { 42 }; // OK
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
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
struct X { X(std::initializer_list<double> v); }; X x{ 1,2,3 };
const 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.
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 };
}
struct A {
std::initializer_list<int> i4;
A() : i4{ 1, 2, 3 } {} // ill-formed, would create a dangling reference
};
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