Declarations

From cppreference.com
< cpp‎ | language

Declarations introduce (or re-introduce) names into the C++ program. Each kind of entity is declared differently. Definitions are declarations that are sufficient to use the entity identified by the name.

A declaration is one of the following:

attr(optional) declarator ;
attr (since C++11) - sequence of any number of attributes
declarator - A function declarator.
This declaration must declare a constructor, destructor, or user-defined type conversion function. It can only be used as part of a template declaration, explicit specialization, or explicit instantiation.
  • block-declaration (a declaration that can appear inside a block), which, in turn, can be one of the following:

Simple declaration

A simple declaration is a statement that introduces, creates, and optionally initializes one or several identifiers, typically variables.

decl-specifier-seq init-declarator-list(optional) ; (1)
attr decl-specifier-seq init-declarator-list; (2)
attr (since C++11) - sequence of any number of attributes
decl-specifier-seq - sequence of specifiers (see below).
init-declarator-list - comma-separated list of declarators with optional initializers. init-declarator-list is optional when declaring a named class/struct/union or a named enumeration

A structured binding declaration is also a simple declaration. (since C++17)

Specifiers

Declaration specifiers (decl-specifier-seq) is a sequence of the following whitespace-separated specifiers, in any order:

  • the inline specifier is also allowed on variable declarations.
(since C++17)
  • the friend specifier, allowed in class and function declarations.
  • the constexpr specifier, only allowed in variable definitions, function and function template declarations, and the declaration of static data members of literal type.
(since C++11)
  • the consteval specifier, only allowed in function and function template declarations.
  • the constinit specifier, only allowed in declaration of a variable with static or thread storage duration. At most one of the constexpr, consteval, and constinit specifiers is allowed to appear in a decl-specifier-seq.
(since C++20)
  • storage class specifier (register, static, thread_local (since C++11), extern, mutable). Only one storage class specifier is allowed, except that thread_local may appear together with extern or static.
  • Type specifiers (type-specifier-seq), a sequence of specifiers that names a type. The type of every entity introduced by the declaration is this type, optionally modified by the declarator (see below). This sequence of specifiers is also used by type-id. Only the following specifiers are part of type-specifier-seq, in any order:
(since C++11)
(since C++17)
  • the keyword class, struct, or union, followed by the identifier (optionally qualified), previously defined as the name of a class, struct, or union.
  • the keyword class, struct, or union, followed by template name with template arguments (optionally qualified, optionally using template disambiguator), previously defined as the name of a class template.
  • the keyword enum followed by the identifier (optionally qualified), previously declared as the name of an enumeration.
only one type specifier is allowed in a decl-specifier-seq, with the following exceptions:
- const can be combined with any type specifier except itself.
- volatile can be combined with any type specifier except itself.
- signed or unsigned can be combined with char, long, short, or int.
- short or long can be combined with int.
- long can be combined with double.
- long can be combined with long.
(since C++11)

Attributes may appear in decl-specifier-seq, in which case they apply to the type determined by the preceding specifiers.

The only specifier that is allowed to appear twice in a decl-specifier-seq is long (which can appear twice). All other repeats, such as const static const, or virtual inline virtual are errors.

(since C++17)

Declarators

init-declarator-list is a comma-separated sequence of one or more init-declarators, which have the following syntax:

declarator initializer(optional) (1)
declarator requires-clause (2) (since C++20)
declarator - the declarator
initializer - optional initializer (except where required, such as when initializing references or const objects). See Initialization for details.
requires-clause(C++20) - a requires-clause, which adds a constraint to a function declaration

Each init-declarator in a init-declarator sequence S D1, D2, D3; is processed as if it were a standalone declaration with the same specifiers: S D1; S D2; S D3;.

Each declarator introduces exactly one object, reference, function, or (for typedef declarations) type alias, whose type is provided by decl-specifier-seq and optionally modified by operators such as & (reference to) or [] (array of) or () (function returning) in the declarator. These operators can be applied recursively, as shown below.

A declarator is one of the following:

unqualified-id attr(optional) (1)
qualified-id attr(optional) (2)
... identifier attr(optional) (3) (since C++11)
* attr(optional) cv(optional) declarator (4)
nested-name-specifier * attr(optional) cv(optional) declarator (5)
& attr(optional) declarator (6)
&& attr(optional) declarator (7) (since C++11)
noptr-declarator [ constexpr(optional) ] attr(optional) (8)
noptr-declarator ( parameter-list ) cv(optional) ref(optional) except(optional) attr(optional) (9)
1) The name that is declared.
2) A declarator that uses a qualified identifier (qualified-id) defines or redeclares a previously declared namespace member or class member.
4) Pointer declarator: the declaration S * D; declares D as a pointer to the type determined by decl-specifier-seq S.
5) Pointer to member declaration: the declaration S C::* D; declares D as a pointer to member of C of type determined by decl-specifier-seq S. nested-name-specifier is a sequence of names and scope resolution operators ::
6) Lvalue reference declarator: the declaration S & D; declares D as an lvalue reference to the type determined by decl-specifier-seq S.
7) Rvalue reference declarator: the declaration S && D; declares D as an rvalue reference to the type determined by decl-specifier-seq S.
8) Array declarator. noptr-declarator any valid declarator, but if it begins with *, &, or &&, it has to be surrounded by parentheses.
9) Function declarator. noptr-declarator any valid declarator, but if it begins with *, &, or &&, it has to be surrounded by parentheses. Note that the outermost function declarator may end with the optional trailing return type. (since C++11)

In all cases, attr is an optional sequence of attributes. When appearing immediately after the identifier, it applies to the object being declared.

cv is a sequence of const and volatile qualifiers, where either qualifier may appear at most once in the sequence.

Notes

When a block-declaration appears inside a block, and an identifier introduced by a declaration was previously declared in an outer block, the outer declaration is hidden for the remainder of the block.

If a declaration introduces a variable with automatic storage duration, it is initialized when its declaration statement is executed. All automatic variables declared in a block are destroyed on exit from the block (regardless how the block is exited: via exception, goto, or by reaching its end), in order opposite to their order of initialization.

Understanding C Declarations

To understand a C declaration follow these rules:

  • Precedence from high to low:
    1. Parentheses grouping together a part of the declaration.
    2. Postfix operators such as () (indicating a function) or [] (indicating an array).
    3. Prefix operators such as * indicating a pointer.
  • If a cv-qualifier (i.e. const or volatile) occurs to the right of an asterisk * (e.g. int * const) then the cv-qualifier applies to the pointer (e.g. int * const is a const pointer-to-int). Otherwise if a cv-qualifier occurs to the left of an asterisk and next to a type specifier then the cv-qualifier applies to the type (e.g. const int * or int const * both refer to a pointer to a const int).

To understand a C declaration such as int const * const * (*p[])(float), we do the following:

  1. Begin at the declaration's name (e.g. p) and say "p is a...". Follow the above precedence rules and going outwards from the name, apply the following:
  2. If to the immediate right there are brackets (e.g. []) then say "an array of...".
  3. If to the immediate right there is an open parenthesis (param-list) (e.g. () or (float, int)) then say "a function with parameters (param-list) and returning...".
  4. If to the immediate left there is an asterisk (and possibly cv-qualifiers) then say "pointer(s) to..." (e.g. for int const * const say "const pointer to const int).
  5. Repeat (2) - (5) as necessary.

So in our example int const * const * (*p[])(float), "p is an array (by 2.) of pointers (by 4.) to functions with parameters (float) and returning (by 3.) a pointer to a const pointer-to-const int," meaning that each pointed-to function has prototype int const * const * funcname(float).

Examples

#include <string>
 
class C {
    std::string member; // decl-specifier-seq is "std::string"
                        // declarator is "member"
} obj, *pObj(&obj);
// decl-specifier-seq is "class C { std::string member; }"
// declarator "obj" defines an object of type C
// declarator "*pObj(&obj)" declares and initializes a pointer to C
 
int a = 1, *p = nullptr, f(), (*pf)(double);
// decl-specifier-seq is int
// declarator a = 1 defines and initializes a variable of type int
// declarator *p = nullptr defines and initializes a variable of type int*
// declarator (f)() declares (but doesn't define)
//                  a function taking no arguments and returning int
// declarator (*pf)(double) defines a pointer to function
//                  taking double and returning int
 
int (*(*foo)(double))[3] = nullptr;
// decl-specifier-seq is int
// 1. declarator "(*(*foo)(double))[3]" is an array declarator:
//    the type declared is "/nested declarator/ array of 3 int"
// 2. the nested declarator is "(*(*foo)(double))", which is a pointer declarator
//    the type declared is "/nested declarator/ pointer to array of 3 int"
// 3. the nested declarator is "(*foo)(double)", which is a function declarator
//    the type declared is "/nested declarator/ function taking double and returning
//        pointer to array of 3 int"
// 4. the nested declarator is "(*foo)" which is a (parenthesized, as required by
//        function declarator syntax) pointer declarator.
//    the type declared is "/nested declarator/ pointer to function taking double
//        and returning pointer to array of 3 int"
// 5. the nested declarator is "foo", which is an identifier.
// The declaration declares the object foo of type "pointer to function taking double 
//     and returning pointer to array of 3 int"
// The initializer "= nullptr" provides the initial value of this pointer.