< cpp‎ | language
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
range-for (C++11)
Jump statements
Function declaration
Lambda function declaration
inline specifier
Dynamic exception specifications (until C++20)
noexcept specifier (C++11)
decltype (C++11)
auto (C++11)
alignas (C++11)
Storage duration specifiers
Alternative representations
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Attributes (C++11)
typedef declaration
Type alias declaration (C++11)
Implicit conversions - Explicit conversions
static_cast - dynamic_cast
const_cast - reinterpret_cast
Memory allocation
Class-specific function properties
explicit (C++11)
Special member functions

Objects, references, functions including function template specializations, and expressions have a property called type, which both restricts the operations that are permitted for those entities and provides semantic meaning to the otherwise generic sequences of bits.


[edit] Type classification

The C++ type system consists of the following types:

  • the type bool;
  • character types:
  • narrow character types:
  • ordinary character types (char, signed char, unsigned char)
  • the type char8_t (since C++20)
  • wide character types (char16_t (since C++11), char32_t (since C++11), wchar_t);
  • signed integer types (short int, int, long int, long long int);
  • unsigned integer types (unsigned short int, unsigned int, unsigned long int, unsigned long long int);
  • lvalue reference to object types;
  • lvalue reference to function types;
  • rvalue reference to object types;
  • rvalue reference to function types;

For every type other than reference and function, the type system supports three additional cv-qualified versions of that type (const, volatile, and const volatile).

Types are grouped in various categories based on their properties:

[edit] Type naming

A name can be declared to refer to a type by means of:

Types that do not have names often need to be referred to in C++ programs; the syntax for that is known as type-id. The syntax of the type-id that names type T is exactly the syntax of a declaration of a variable or function of type T, with the identifier omitted, except that decl-specifier-seq of the declaration grammar is constrained to type-specifier-seq, and that new types may be defined only if the type-id appears on the right-hand side of a non-template type alias declaration.

int* p;               // declaration of a pointer to int
static_cast<int*>(p); // type-id is "int*"
int a[3];   // declaration of an array of 3 int
new int[3]; // type-id is "int[3]" (called new-type-id)
int (*(*x[2])())[3];      // declaration of an array of 2 pointers to functions
                          // returning pointer to array of 3 int
new (int (*(*[2])())[3]); // type-id is "int (*(*[2])())[3]"
void f(int);                    // declaration of a function taking int and returning void
std::function<void(int)> x = f; // type template parameter is a type-id "void(int)"
std::function<auto(int) -> void> y = f; // same
std::vector<int> v;       // declaration of a vector of int
sizeof(std::vector<int>); // type-id is "std::vector<int>"
struct { int x; } b;         // creates a new type and declares an object b of that type
sizeof(struct{ int x; });    // error: cannot define new types in a sizeof expression
using t = struct { int x; }; // creates a new type and declares t as an alias of that type
sizeof(static int); // error: storage class specifiers not part of type-specifier-seq
std::function<inline void(int)> f; // error: neither are function specifiers

The declarator part of the declaration grammar with the name removed is referred to as abstract-declarator.

Type-id may be used in the following situations:

Type-id can be used with some modifications in the following situations:

  • in the parameter list of a function (when the parameter name is omitted), type-id uses decl-specifier-seq instead of type-specifier-seq (in particular, some storage class specifiers are allowed);
  • in the name of a user-defined conversion function, the abstract declarator cannot include function or array operators.

[edit] Elaborated type specifier

Elaborated type specifiers may be used to refer to a previously-declared class name (class, struct, or union) or to a previously-declared enum name even if the name was hidden by a non-type declaration. They may also be used to declare new class names.

See elaborated type specifier for details.

[edit] Static type

The type of an expression that results from the compile-time analysis of the program is known as the static type of the expression. The static type does not change while the program is executing.

[edit] Dynamic type

If some glvalue expression refers to a polymorphic object, the type of its most derived object is known as the dynamic type.

// given
struct B { virtual ~B() {} }; // polymorphic type
struct D: B {}; // polymorphic type
D d; // most-derived object
B* ptr = &d;
// the static type of (*ptr) is B
// the dynamic type of (*ptr) is D

For prvalue expressions, the dynamic type is always the same as the static type.

[edit] Incomplete type

The following types are incomplete types:

  • the type void (possibly cv-qualified);
  • incompletely-defined object types:

All other types are complete.

Any of the following contexts requires type T to be complete:

(In general, when the size and layout of T must be known.)

If any of these situations occur in a translation unit, the definition of the type must appear in the same translation unit. Otherwise, it is not required.

An incompletely-defined object type can be completed:

  • A class type (such as class X) might be incomplete at one point in a translation unit and complete later on; the type class X is the same type at both points:
struct X;             // X is an incomplete type
extern X* xp;         // xp is a pointer to an incomplete type
void foo() {
  xp++;               // ill-formed: X is incomplete
struct X { int i; };  // now X is a complete type
X x;
void bar() {
  xp = &x;            // OK: type is “pointer to X”
  xp++;               // OK: X is complete
  • The declared type of an array object might be an array of incomplete class type and therefore incomplete; if the class type is completed later on in the translation unit, the array type becomes complete; the array type at those two points is the same type.
  • The declared type of an array object might be an array of unknown bound and therefore be incomplete at one point in a translation unit and complete later on; the array types at those two points ("array of unknown bound of T" and "array of N T") are different types.

The type of a pointer to array of unknown bound, or to a type defined by a typedef declaration to be an array of unknown bound, cannot be completed.

extern int arr[];   // the type of arr is incomplete
typedef int UNKA[]; // UNKA is an incomplete type
UNKA* arrp;         // arrp is a pointer to an incomplete type
UNKA** arrpp;
void foo() {
  arrp++;           // error: incomplete type
  arrpp++;          // OK: sizeof UNKA* is known
int arr[10];        // now the type of arr is complete
void bar() {
  arrp = &arr;      // error: different types
  arrp++;           // error: UNKA can’t be completed

[edit] See also