Every object, reference and function in C++ has a type, fixed at compile time. Every expression has both a type and a value category [basic.types], [basic.lval]. This page covers the kinds of type, how qualifiers change them, and how values convert between them.
Fundamental types
The types built into the language are listed in [basic.fundamental].
| Group | Types | Notes |
|---|---|---|
| Void | void | An incomplete type with no values, used for functions that return nothing |
| Null pointer | std::nullptr_t | The type of nullptr (C++11) |
| Boolean | bool | Holds true or false. Its size is implementation-defined |
| Narrow character | char, signed char, unsigned char, char8_t (C++20) | Three distinct types for char; whether plain char is signed is implementation-defined. sizeof of each is 1 |
| Wide character | wchar_t, char16_t, char32_t (C++11) | char16_t and char32_t have the width of uint_least16_t and uint_least32_t |
| Standard signed integer | signed char, short, int, long, long long (C++11) | Two’s complement since C++20 |
| Standard unsigned integer | The unsigned form of each of the above | Arithmetic is modulo 2N |
| Standard floating-point | float, double, long double | Ranges nest, in that order |
| Extended floating-point | std::float16_t, std::float32_t, std::float64_t, std::float128_t, std::bfloat16_t | Optional aliases from <stdfloat> (C++23) [basic.extended.fp] |
Integer widths
The standard sets a minimum width for each integer type. Since C++20, the value range of an N-bit signed type is exactly -2N-1 to 2N-1-1.
| Type | Minimum width in bits |
|---|---|
signed char | 8 |
short | 16 |
int | 16 |
long | 32 |
long long | 64 |
The actual widths are set by the platform’s data model.
| Model | int | long | Pointer | Used by |
|---|---|---|---|---|
| ILP32 | 32 | 32 | 32 | 32-bit Windows and Linux |
| LP64 | 32 | 64 | 64 | 64-bit Linux and macOS |
| LLP64 | 32 | 32 | 64 | 64-bit Windows |
For an exact width, use the aliases in <cstdint>:
std::int32_t, std::uint64_t and the others. The exact-width
aliases are optional. std::size_t is the type of sizeof,
and std::ptrdiff_t is the type of a pointer subtraction, both in
<cstddef>. std::byte (C++17) is an enumeration used for
raw memory, and is not a character or integer type.
Compound types
Compound types are built from other types [basic.compound].
| Kind | Written | See |
|---|---|---|
| Pointer to object or function | T* | [dcl.ptr] |
| Lvalue reference | T& | References |
| Rvalue reference | T&& | References |
| Pointer to member | T C::* | [dcl.mptr] |
| Array | T[N] | [dcl.array] |
| Function | R(Args...) | Functions |
| Class, struct, union | class C { }; | Classes |
| Enumeration | enum E { }; | Enumerations |
- An array’s bound is a constant expression and part of its type, so
int[3]andint[4]are different types. There are no variable-length arrays in standard C++. - An array cannot be copied by assignment, and it decays to a pointer to its first
element in most expressions. Use
std::arraywhere a real value is wanted. - A type is incomplete if its size is not known, as with
void, an array of unknown bound, or a declared but undefined class. void*can point to any object type, but cannot be dereferenced.
Enumerations
enum-specifier:
enum-head { [enumerator-list] }
enum-head { enumerator-list , }
enum-head:
enum-key [attribute-specifier-seq] [enum-head-name] [enum-base]
enum-key: enum | enum class | enum struct
enum-base: : type-specifier-seq
Unscoped enum | Scoped enum class (C++11) | |
|---|---|---|
| Names visible | In the enclosing scope | Only as E::name |
| Converts to integer | Implicitly | Only by static_cast |
| Underlying type | Chosen by the compiler if not given | int if not given |
| Can be forward-declared | Only with a fixed underlying type | Yes |
Writing : type fixes the underlying type. using enum E;
(C++20) brings a scoped enumeration’s names into the current scope [enum.udecl].
enum Colour { red, green, blue }; // unscoped, names in enclosing scope
enum class Mode : std::uint8_t { read, write };
enum class Flag; // forward declaration, underlying int
int n = red; // fine: implicit conversion
int m = static_cast<int>(Mode::read); // a scoped enum needs a cast
void f(Mode m) {
switch (m) {
using enum Mode; // C++20
case read: break;
case write: break;
}
}
const and volatile
A type may be cv-qualified [basic.type.qualifier], [dcl.type.cv]. A non-qualified type and its qualified forms are different types with the same representation.
| Qualifier | Meaning |
|---|---|
const | The object may not be modified through this name. Modifying an object defined const is undefined behaviour |
volatile | Each read and write is observable behaviour, and may not be optimised away or reordered with other volatile accesses. Used for memory-mapped hardware. It does not make access safe between threads |
const volatile | Both |
A qualifier applies to the thing to its left, or, if nothing is on its left, to the thing to its right. Read pointer declarations from right to left.
const int* a; // pointer to const int: *a cannot be assigned
int const* b; // the same type as a
int* const c = &x; // const pointer to int: c itself cannot be reseated
const int* const d = &x; // const pointer to const int
const applies to the object itself; low-level const applies to what it points tomutable on a data member allows it to be changed even through a
const object, as described on the
Classes page.
Since C++20 some uses of volatile are deprecated.
References
A reference is an alias for an object or function [dcl.ref]. It has no storage of its own in the language, must be initialised, and cannot be reseated. There are no references to references, no arrays of references and no pointers to references.
| Form | Binds to |
|---|---|
T& | Lvalues of T |
const T& | Lvalues and rvalues of T, and anything convertible to T |
T&& | Rvalues of T, and anything convertible to T through a temporary |
const T&& | Rvalues, rarely used |
Reference collapsing
A reference to a reference can arise only through a template parameter or a type alias. It
then collapses [dcl.ref]. Only && on &&
stays an rvalue reference.
| Applied to | & applied | && applied |
|---|---|---|
T& | T& | T& |
T&& | T& | T&& |
This makes forwarding references possible. In a function template,
T&& with T deduced from an lvalue gives
T = U&, and the parameter collapses to U&. See
Templates.
Temporary lifetime extension
A temporary bound directly to a local reference lives as long as that reference [class.temporary].
std::string make();
const std::string& a = make(); // lifetime extended to the end of a's scope
std::string&& b = make(); // the same, and b is modifiable
const char* c = make().c_str(); // dangling: the temporary dies at the semicolon
A temporary bound to a reference parameter lasts until the end of the full expression
that contains the call. A temporary bound in a return statement is not extended.
Since C++23 temporaries in a range-based
for initialiser last for the whole loop.
Value categories
Every expression has a value category [basic.lval]. The three primary categories are lvalue, xvalue and prvalue. Two more group them: a glvalue is an lvalue or an xvalue, and an rvalue is a prvalue or an xvalue.
| Category | Meaning | Examples |
|---|---|---|
| lvalue | Names an object or function, and has an identity | x, *p, a[i], "text", a call that returns T& |
| xvalue | An object about to be moved from | std::move(x), a call that returns T&&, a.b where a is an rvalue |
| prvalue | Computes a value, or initialises an object | 42, x + 1, nullptr, a call that returns by value, a lambda expression |
A string literal is an lvalue, because it is an array with storage. The name of an rvalue reference
variable is an lvalue, because it has a name: std::move or
std::forward is what turns it back into an rvalue.
Since C++17 a prvalue is a recipe rather than an object. A prvalue initialises its destination directly, which is what makes copy elision guaranteed. Where a real object is needed, the temporary materialisation conversion [conv.rval] turns a prvalue into an xvalue.
auto and decltype
auto deduces a type from an initialiser [dcl.spec.auto],
[dcl.type.auto.deduct]. Deduction works like template argument deduction, so
references and top-level const are dropped unless written.
const int& r = n;
auto a = r; // int: drops const and reference
auto& b = r; // const int&
const auto c = r; // const int
auto&& d = n; // int&: a forwarding reference with an lvalue
auto e = {1, 2, 3}; // std::initializer_list<int>
auto f{1}; // int (since C++17)
auto g = "text"; // const char*
auto givesdecltype (C++11) gives the type of an entity or expression without
evaluating it [dcl.type.decltype].
| Operand | Result |
|---|---|
| An unparenthesised name or member access | The declared type of that entity |
Any other lvalue expression of type T | T& |
Any other xvalue expression of type T | T&& |
Any other prvalue expression of type T | T |
int x = 0;
decltype(x) a; // int
decltype((x)) b = x; // int&: parentheses make it an lvalue expression
decltype(auto) c = (x); // int& (C++14): deduces with decltype's rules
auto twice(int v) { return v * 2; } // return type deduced (C++14)
auto add(int a, int b) -> int { return a + b; } // trailing return type (C++11)
auto(x) and auto{x} (C++23) make a prvalue copy of
x, decayed as function parameter passing would.
Type properties
Several properties decide what can be done with objects of a type [class.prop], [basic.types.general].
| Property | Means |
|---|---|
| Trivially copyable | Copying the bytes copies the object, as with std::memcpy or std::bit_cast |
| Standard-layout | Member layout that is compatible with C: all non-static data members have the same access, there are no virtual functions or virtual bases, and members are declared in one class of the hierarchy |
| Aggregate | Can be initialised member by member with braces; see Declarations |
| Implicit-lifetime | Objects may be created implicitly by operations such as std::malloc; see Undefined behaviour |
| Literal | Usable in constant expressions |
Alignment is the set of address boundaries a type requires [basic.align].
alignof(T) gives it and alignas(n) strengthens it [dcl.align].
Conversions
A standard conversion sequence is at most one conversion from each row group below, in that order [conv]. A conversion may also be user-defined; at most one user-defined conversion is applied implicitly in a chain.
| Order | Conversion | Effect | Section |
|---|---|---|---|
| 1. Lvalue transformation | Lvalue-to-rvalue | Reads the value of a glvalue | [conv.lval] |
| Array-to-pointer | An array decays to a pointer to its first element | [conv.array] | |
| Function-to-pointer | A function decays to a pointer to itself | [conv.func] | |
| 2. Promotion or conversion | Integral promotion | A small integer type becomes int or unsigned int | [conv.prom] |
| Floating-point promotion | float becomes double | [conv.fpprom] | |
| Integral conversion | Any other integer type change | [conv.integral] | |
| Floating-point conversion | Any other floating-point type change | [conv.double] | |
| Floating-integral conversion | Between floating-point and integer | [conv.fpint] | |
| Pointer, pointer-to-member and boolean conversions | Derived to base pointer, nullptr to pointer, anything scalar to bool | [conv.ptr], [conv.mem], [conv.bool] | |
| 3. Function pointer | Function pointer conversion | Drops noexcept from a function pointer (C++17) | [conv.fctptr] |
| 4. Qualification | Qualification conversion | Adds const or volatile, as with int* to const int* | [conv.qual] |
- Converting to an unsigned type is modulo 2N. Since C++20 converting to a signed type is too; earlier it was implementation-defined.
- Converting a floating-point value to an integer truncates towards zero. The behaviour is undefined if the result does not fit.
- Converting to
boolgivesfalsefor zero, null pointer and null member pointer, andtrueotherwise.
Integer promotions
Integer types with a conversion rank lower than int, as well as
bool, promote to int if it can represent all their values, and otherwise to
unsigned int [conv.prom]. wchar_t, char8_t,
char16_t, char32_t and unscoped enumerations without a fixed
underlying type promote to the first of int, unsigned int,
long, unsigned long, long long and
unsigned long long that can hold all their values. An unscoped
enumeration with a fixed underlying type promotes to that type.
Usual arithmetic conversions
Binary operators bring their operands to a common type [expr.arith.conv]:
- If either operand is a scoped enumeration, no conversion is performed, and the expression is ill-formed unless the other operand is the same type.
- Otherwise, if one operand is floating-point and the other is not, the other converts to the floating-point type. If both are floating-point, the operand of lesser floating-point conversion rank converts to the type of the other.
- Otherwise integer promotions are applied to both, and then:
- if the types are the same, that is the type;
- if both are signed or both unsigned, the one of greater rank wins;
- if the unsigned operand’s rank is greater or equal, the signed operand converts to the unsigned type;
- if the signed type can hold all values of the unsigned type, the unsigned operand converts to the signed type;
- otherwise both convert to the unsigned counterpart of the signed type.
int a = -1;
unsigned b = 1;
bool c = a < b; // false: a converts to unsigned, a very large number
Narrowing
Brace initialisation forbids narrowing conversions [dcl.init.list]: a floating-point type to
an integer type, a larger floating-point type to a smaller one unless the value is a
constant that fits, an integer type to a floating-point type unless the value is a
constant that fits, and an integer type to one that cannot represent all its values
unless the value is a constant that fits. Pointer to bool is narrowing too
(since C++20).