Igor's C++ Grimoire



Igor's C++ Grimoire aims to be a reasonably complete reference to C++11, C++14 and C++17. C++20 additions are a work in progress; what there is is limited and subject to change (in particular, the section on Coroutines is likely to change significantly in the future). There are also some C++20 specific cross-references that will not resolve at present

Many sources have been used to compile it, but the major ones, and some of particular note are as follows. The C++ Programming Language 4th Ed, by Bjarne Stroustrup. Effective C++ 3rd Ed, and Effective Modern C++ 1st Ed, November 2014, both by Scott Meyers. On-line resources include ISO C++ FAQ, cppreference.com, CppCon and many others

The original intention was to just present the facts; the syntax, some examples, and a thorough description of the language with the aim of being as complete and unambiguous as possible whilst keeping the fluff to a minimum. As time has marched on, considerable 'other stuff' has been added particularly in the area of advice on avoiding common (and sometimes obscure) pitfalls, particular techniques, and general advice that should make life easier, Notable exceptions to the original ethos of only the facts are the Templates and Meta-Programming sections which almost entirely describe the application of technique rather than syntax, and the Design Considerations section which is mostly made-up of (often other people's) rambling thoughts and ideas

The standard library is not described in any detail. Exceptions are the Concurrency support (which is almost entirely implemented by standard library components), and some basic components and/or particular types/functions that are referred to by other items. These are all described in the Standard Techniques section and the sections that follow it. Be aware though, that most of this latter section provides only cursory descriptions, and those descriptions that are more complete may not be absolutely comprehensive

This document is intended as a reference; not a tutorial. As such, early sections often refer to concepts that are not introduced until later; hopefully the extensive cross-referencing will help in this respect

Hints, tips, and any musings that wander away from the narrow criteria of simply facts are highlighted in a box like this one. None of this information is necessary in order to understand C++, but there are definitely some very good ideas and a number of "gotcha's" here that are worth knowing about. Useful hints are shown with the symbol shown at the start of this paragraph

Warnings and general observations of a potentially problematic nature are shown with this symbol

Significant or obscure causes of error are shown with this symbol

Always do this if you want your code to work correctly and/or avoid future problems

Never do this if you want your code to work correctly and/or avoid future problems

Igor's opinion (unadulterated bias!); often polarising and the source of countless arguments. Nod in sage agreement/rant at your computer in vitriolic disgust, as you see fit

Specific points in the main text may also be highlighted with one of the above symbols as appropriate

Items that only relate to a specific C++ version are identified with , , etc, as appropriate. Whilst no attempt is made to document versions earlier than C++11, there is the odd item that is specific to C++98/03. These are identified with

Features that apply in all versions upto and including a particular version are identified with , , , etc

Features that have been introduced or modified in a particular version and retained in all subsequent versions are identified with , , , etc, though in the case of (indicating a change since C++98/03), only major features are identified this way

A few non-standard (but commonly supported) features are described, and these are identified with

Any points that are incomplete, seemingly inconsistent or based on ambiguous source material are marked with . Hovering over these markers should display further details

All but the most trivial of code examples have been compiled, run, and shown to work. However, they typically omit the required header file inclusions, and may require some using declarations where components from the std namespace have not been qualified (which is often the case)

References within this document may be textual links or may be shown as just tiny links like (which should pop-up a destination hint if hovered over). Many links refer to very specific items, paragraphs or examples appropriate to the context of the referrer

If you spot any errors, inconsistencies, or think something has been missed out or is incomplete or ambiguous then please let me know; no-matter how trivial the issue may seem. You can email me at igorknockknock.org.uk (note that this will not cut-and-paste correctly - that's deliberate. Also, take care spelling 'knockknock'!)

You are free to make copies of this document for your own use. You may not publicly republish it. Distribution within an organisation may only be in its original, unmodified form. You may not charge for it

At the time of writing (see the date at the top), it seems that the Chrome browser and other browsers based on Chrome do not render some elements of this document correctly; this is a particular issue with the code examples, parts of which are often rendered on a single rather than multiple lines. The Firefox browser renders all elements correctly

If you enable Javascript, some controls shall appear to help with navigation and shall be described here

A number of navigation tools are provided by the buttons on the right of the display;-

Quick link to list of contents
Quick link to the index
Toggles between normal left-click operation and 'panning' (enables the page to be pushed up and down)

Right-clicking one of these buttons once shall allow placement of a bookmark in the text; after right-clicking the button, move to where you would like the bookmark and ('left' or 'right') click again. The bookmark will not be placed if the selection is too vague (eg, you can't bookmark the whole document)

Once a bookmark is placed, a is placed in the left margin and its button is changed to a

A previously placed bookmark may be repositioned by repeating the same procedure

A bookmark may be deleted by double-right-clicking the appropriate bookmark button

To jump to a placed bookmark, left-click the appropriate button

  Many of the cross-reference links refer to very specific sections or paragraphs. This symbol is placed in the left margin to identify the link destination

Code Structure



A trigraph is a series of three characters which represent a single other character. The full collection of supported trigraphs are;-


Unless you absolutely have to, do not use trigraphs. However, be aware of them as they can be accidentally specified, thus leading to unexpected results


A digraph is a series of two characters which represent a single other character. The full collection of supported digraphs are;-


As with trigraphs, unless you absolutely have to, do not use digraphs, but be aware of them

Source Files

Apart from the maintainability and management improvements that come from dividing a program into multiple files, the technique improves modularity, enhances logical structure, and allows separation of interfaces from implementation

The text files are generally divided into two groups; implementation files and interface (header) files. Implementation files will typically include one or more header files, and header files may (and often do) also include other header files. This distinction is rendered a little fuzzy in practice because it is common for header files to define implementation in the form of inline functions, template definitions, etc

A compiler shall typically deal with each implementation file in turn. It will first invoke the preprocessor to create a translation unit. It is to this that the C++ language rules are applied. The translation unit is compiled into object code. All the individual object code parts are then passed to a linker to form the final executable code

Minimise compilation dependencies between files

A class declaration is usually also its definition, and that includes its interface plus significant implementation detail in the form of data member declarations and even private member functions

Therefore, when a client wanting to use the class includes the header file that provides the definition, it implicitly creates a dependency between itself and the types and values used in the class' implementation details. This creates a number of problems;-

The basic principle that leads to reduced compilation dependency is to replace definitions with declarations wherever possible. To achieve this;-

Template Code Organisation

There are some special considerations when dealing with templates. In particular, there are two rules with regard to template compilation;-

Probably the most common code organisation approach is to include the same template definition into all translation units and rely on the compiler to optimise-away all duplicate specialisations; the "include everywhere" technique;-

#include <my_template.h> // ...use the template...

One problem with this approach is that it tends to (unintentionally) encourage undesirable dependencies to grow between the user-code and the template definition

This problem can be mitigated by taking the approach of "include template definitions later (after they are used)". This can be achieved by dividing the template into a declaration .h file, and a definition .cpp file, and then arranging the translation unit thus;-

#include <my_template.h> // ...use the template... #include <my_template.cpp>

This minimises the changes of the template definition having some unanticipated and detrimental effect on the user code, but makes the reverse risk greater

Although most will, an implementation is not required to be able to delete duplicate/redundant copies of a template instantiation. This can lead to "multiple definition" errors at link-time

An implementation is not required to analyse duplicate/redundant copies of a template instantiation prior to deleting duplicates. This highlights the importance of ensuring that all instantiations for a specialisation are identical, so that whichever ones are discarded, the result will be the same

Regardless of how clever the compiler is, in a large application, building the multiple instantiations only to throw them away later can increase build times considerably

Standard Headers

An implementation may be "hosted" or "free-standing"; the former includes all the standard library headers by default. The latter does not, but must support at least the header files highlighted (eg, <cstddef>) as a minimum

Everything defined in these headers is in the std namespace, so the definitions within them must be explicitly qualified or appropriate using-declarations and/or a using-directive must be used to bring them into scope

C Library (these all follow the same naming pattern based on the 'C' header file name )
<cassert>Diagnostics (assert.h)
<cctype>Character handling functions (ctype.h)
<cerrno>Errors (errno.h)
<cfenv>Floating-point environment (fenv.h)
<cfloat>Characteristics of floating-point types (float.h)
<cinttypes>Integer types (inttypes.h)
<ciso646>ISO 646 alternative operator spellings (iso646.h)
<climits>Sizes of integral types (limits.h)
<clocale>Localization library (locale.h)
<cmath>Maths library (math.h)
<csetjmp>Non-local jumps (setjmp.h)
<csignal>Signal handling library (signal.h)
<cstdalign>__alignas_is_defined (stdalign.h) - ( Deprecated)
<cstdarg>Variable arguments handling (stdarg.h)
<cstdbool>Boolean type (stdbool.h) - ( Deprecated)
<cstddef>Standard definitions (stddef.h)
<cstdint>Integer types (stdint.h)
<cstdio>Standard I/O (stdio.h)
<cstdlib>Standard general utilities library (stdlib.h)
<cstring>Strings and memcpy (string.h)
<ctgmath>Type-generic maths (tgmath.h) - ( Deprecated)
<ctime>Time Library (time.h)
<cuchar>Unicode characters (uchar.h)
<cwchar>Wide characters (wchar.h)
<cwctype>Wide character type (wctype.h)
<map>map, multimap
<queue>queue, priority_queue
<set>set, multiset
<unordered_map>unordered_map, unordered_multimap
<unordered_set>unordered_set, unordered_multiset
<vector>vector, vector<bool>
I/O Streams
<ios>ios_base, ios
<istream>istream, iostream
<iostream>cin, cout, cerr, clog
<fstream>ifstream, fstream, ofstream, filebuf
<sstream>istringstream, stringstream, ostringstream, stringbuf
<filesystem>File system interface
<atomic>atomic, atomic_flag, memory_order
<condition_variable>condition_variable, condition_variable_any, cv_status
<thread>thread, this_thread
<algorithm>Standard algorithms
<chrono>duration, time_point, system_clock, steady_clock, high_resolution_clock
<codecvt>Unicode conversion facets
<complex>Complex numbers library
<exception>Standard exceptions
<functional>Function objects
<initializer_list>Initializer list
<limits>Numeric limits
<memory>allocator, allocator_arg, etc, unique_ptr, shared_ptr, weak_ptr, unique_ptr, default_delete, make_shared, etc
<new>Dynamic memory handling
<numeric>Generalized numeric operations
<random>Random number generation
<regex>Regular Expressions
<stdexcept>Standard exception types
<string>string, u16string, u32string, wstring
<system_error>System errors
<typeinfo>Type information
<utility>Utilities; pair, relational operators, rvalue handling (forward, move, move_if_noexcept, etc), swap
<valarray>valarray - supports arrays of numeric values

'C' Functions

To access an external 'C' function from C++, use the following;-

extern "C" int fn(void);

A group declaration may also be made in order to create a linkage block;-

extern "C" { extern int a; void fn2(int a); // ...etc... }

Assuming the above is within a header file, it is more common to see it written as follows; to avoid errors when processed by a C compiler;-

#ifdef __cplusplus extern "C" { #endif extern int a; void fn2(int a); // ...etc... #ifdef __cplusplus } #endif

Inline Assembler

Assembler code may be embedded into C++ source code with the asm statement;-



The entry point of any C++ program is the function main(). It's prototype is the same as for plain 'C'. In fact, two prototypes are supported. A program must specify only one of them;-

int main(); // Short form int main(int argc, char* argv[]); // Full/long form int main(int argc, char* argv[], char* envp[]);


Program Termination

A program shall terminate if any of the following occurs;-

In any implementation, there are probably other ways of terminating a program such as division by zero, illegal memory access, etc

The plain 'C' (and C++) standard library function std::atexit() may be used to register a function that should be executed on normal program termination. For example;-

// My cleanup function void my_cleanup(); // ... // Register my cleanup function if (!atexit(&my_cleanup)) { // Cleanup function registered ok } else { // Error: Too many cleanup functions registered }


There are a number of pre-processor directives. Parsing these can (and likely does) result in code modification or compiler parameter modification. Only after all pre-processor directives are parsed is the compiler presented with the resulting code

All pre-processor directives start with the character #. Here is a list of them;-

#include filename

Replace the #include line with the contents of the specified file. This is used to bring-in header files into code files, or into other header files. #include directives can be nested (a header files can be 'included' that itself includes other header files)

There are two formats for specifying filename; by using <name> or by using "name". The former syntax uses the compiler's include path, and the latter is a path relative to the compiler's current directory path. However, this distinction can be somewhat blurred in some implementations. Here are some examples;-

#include <cstddef> // Include the standard header file cstddef #include "./includes/common_defs.h" // Include local header file // ./includes/common_defs.h

The standard header names do not have a .h extension, hence <cstddef> and not <cstddef.h>

#define symbol value

Define a symbol with a specified value. Equivalent to using the -D option on the command line of most compilers. For example;-

#define HELLO Hello-World

…would define the symbol HELLO with the value Hello-World. Whether the value makes sense or not will depend on the context it is subsequently used in

#define symbol(arguments) value

Define a macro; a pre-processor symbol that takes arguments. For example;-

#define FN_CALL(fn, a, b) \ (fn(a * b)) // Use the macro FN_CALL(sleep, 5, 7); // This expands out to 'sleep(5 * 7);'

It is possible to make a macro take a variable number of arguments by using ... in the argument list and __VA_ARGS__ within the macro body

All the above examples define their values within (). Not doing this is legal but often causes errors as the expanded result creates an unexpected expression. The above also shows the use of \ as a continuation character. Macros may extend over many lines by terminating all the lines (except the last) with \

Macros cannot recurs and macro names cannot be overloaded. If adding comments to macros, use the /**/ syntax as some older tools may not understand the //… form

Macros may use the # and ## operations (described below)

#undef symbol Undefine a symbol/macro previously defined with #define
#line line-num
#line line-num filename
#line other

Overrides the values returned by __FILE__/__LINE__, and reported by the compiler. line-num is a positive integer and filename follows normal preprocessor rules for a string constant. If the supplied parameters do not match the standard formats then the supplied format is macro-expanded; the result being expected to match one of the two standard formats

#if expression

Defines a section of code that is to be parsed if the specified expression equates to true. The section of code extends to the next #else, #elif or #endif. If the expression equates to false then the section of code is removed/ignored. The following forms are allowed for expression (the () are optional but improve readability);-

The logical operators || and &&, and the relational operators ==, !=, <, >, <= and >= may be used in the expression. () may be used to force evaluation ordering

(1)If the literal 1 ≠ zero (obviously it is)
(A)If symbol A is defined as having an integral value of ≠ zero
(B == 42)If symbol B = 42
defined(A)If symbol A is defined. Equivalent to #ifdef A
__has_include(<blob>) If the header file <blob> exists ( Not an indication that the file has already been included)

Note that expression must resolve to an integral value; specifying a string or floating point shall either yield a pre-processor error or shall parse but will not generate the intended result. Note specifically that operations such as sizeof() are not allowed; they are not understood by the pre-processor

#elif expression Defines an 'else if'. This is generally used to create 'if-else-if' ladders and defines an alternate branch to a preceding #if or #elif pre-processor statement
#ifdef symbol Defines a section of code that is to be parsed if the specified symbol has been previously defined by a #define pre-processor statement. The section of code extends to the next #else or #endif. If the symbol has not been defined then the section of code is removed/ignored
#ifndef symbol This is the same as #ifdef except the logic is reversed
#else Defines an alternate (false) branch to a preceding #if (or one of the variants)
#endif Terminates the optional section indicated by a preceding #if (or one of the variants) or else
# symbol

Convert symbol into a string by bracketing it with " characters. For example;-


…would yield;-

symbol ## symbol

Concatenates two symbols. For example;-

Hello- ## World

…would yield;-

#pragma option Sets a compiler-specific option. The format of option depends on the option and the compiler. Unrecognised/unsupported #pragma directives should be ignored by the pre-processor
symbol Specifying a symbol (previously defined by a #define statement) on its own shall cause it to be expanded/replaced in-situ with its defined value
#error text Generates a compiler error, typically outputting text
#warning text

Generates a compiler warning, typically outputting text

This is a non-standard directive but is supported by several implementations. Consider using the more widely supported #pragma message instead

#pragma message ("text")

Outputs text at compile-time

This is a non-standard directive but is widely supported. Some implementations will simply output the message during compilation. Some will output the message and treat it as a compilation warning. Some will treat the message as a warning only if text begins with the word warning

This has the advantage over using #warning in that if it is not supported, it won't cause an error (unrecognised #pragma directives should be ignored by the pre-processor)

Using the pre-processor #define statement to define macros (ie, something that will expand to a piece of C++ code) is ugly and can be the cause of many errors. Don't do it; use constant expressions instead

Notwithstanding this, there are a couple of legitimate uses of macros; to support conditional compilation and to protect against recursive inclusion

To support conditional compilation. Limit #define statements to setting control values that will only be used by subsequent #if statements, and NOT to embed into C++ code. For example;-

// Compile-in experimental feature by setting this to '1' #define USE_EXPERIMENTAL_FEATURE (1) #if (USE_EXPERIMENTAL_FEATURE == 1) // Some code that implements our experimental feature #endif

How to protect against repeated inclusion in a header file

Pre-Defined Macros

The compiler defines a number of macros which may be used within code. Some of these are very useful, especially for debugging

__cplusplus Indication of C++ compilation (rather than plain 'C'). Its value is 201103L / 201402L / 201703L / 202002L. Some intermediate value may be defined if an implementation is not fully compliant; for example, if a C++20 implementation includes some, but not all, C++20 features, a value somewhere between 201703L and 202002L is likely to be defined. See also __STDC__
__DATE__ Current date in the format Mm dd yyyy  Beware the American-specific format
__TIME__ Current time in the format hh:mm:ss
__FILE__ Name of current source file. See also #line
__LINE__ Line number of source code within current file. See also #line
__func__ Name of the current function. This is a 'C'-style string and implementation defined. This is defined only within the body of a function and technically is not actually a macro (though this distinction doesn't matter in use)
__STDC_HOSTED__ Has a value of 1 if the implementation is hosted, 0 (zero) otherwise
__STDCPP_DEFAULT_NEW_ALIGNMENT__ Integer of type std::size_t that defines the minimum byte alignment guaranteed by the default implementation of operator new

Some other macros are conditionally defined by the implementation;-

__STDC__ Indication of plain 'C' compilation (rather than C++). See also __cplusplus
__STDC_VERSION__ May or may not be defined. Implementation-specific
__STDC_MB_MIGHT_NEQ_WC__ Set to 1 if, in the encoding for wchar_t, a member of the basic character set might have a code value that differs from its value as an ordinary character literal
__STDC_ISO_10646__ An integer in the format yyyymmL. If defined then it indicates that all characters in the Unicode required set, when stored in a wchar_t type, have the same value as the short identifier of each character. The Unicode required set is specified by ISO/IEC 10646; the version being adhered to being specified by yyyymm
__STDCPP_STRICT_POINTER_SAFETY__ Set to 1 if the implementation has strict pointer safety. Otherwise it is undefined. The function std::get_pointer_safety() returns an enumeration indicating similar information. This is only of relevance if the implementation supports and uses garbage collection
__STDCPP_THREADS__ Set to 1 if a program can have more than one thread of execution. otherwise it is undefined. See also Concurrency


Comments are defined as follows. There are two forms; the traditional 'C' comments which start with /* and end with */. Such comments may extend for as many lines as required. There is also the C++ comment style. This allows single-line comments only, starts with // and ends with the end-of-line character

/* This is a 'C'-style single-line comment */ /* This is a 'C'-style multi- * line comment */ // This is a 'C++'-style single-line comment

Reserved Words

Here is a complete list of reserved keywords;-


In addition, the keyword export is reserved but currently not used. It was originally intended to facilitate template definition/declaration separation, but the idea failed

The following contextual keywords are defined;-



Attributes provide a means of tagging certain names, types, functions, etc as having particular features. The following standard attributes are defined (there are also several non-standard attributes in common use);-

[[likely]] and [[unlikely]] 


The [[deprecated]] attribute may be used to mark a name as deprecated. The name can still be used, but such marking gives the compiler an opportunity to issue a warning of its use

There are two forms of this attribute;-

[[deprecated]] [[deprecated("reason text)]]

This attribute may be applied to the following;-

Class declaration

Deprecate a class type;-

class [[deprecated]] X { // ... };

The placement of [[deprecated]] is important; the following will deprecate the variable a, and not the class Y

[[deprecated]] class Y { // ... } a;
  • Class functions and variables may also be deprecated as shown below
Name alias
using car [[deprecated]] = int; [[deprecated]] typedef int colour;

Variable name

Class data member

[[deprecated]] int a;

To deprecate all variables within a multi-declaration;-

[[deprecated]] int a, b, c;

To deprecate one variable within a multi-declaration;-

int a [[deprecated]], b, c;
Structured binding
[[deprecated]] auto [x, y] = a;

Function declaration

Class member function

Friend function

Function argument

To deprecate a function name;-

[[deprecated]] void fn();

To deprecate a function argument. For this to be meaningful (ie, to allow the user to avoid the deprecated argument), an overload of the deprecated form would likely to also need defining;-

void fn(int a, [[deprecated]] int b); // The 'new' (overloaded) function without the deprecated argument void fn(int a);
Namespace name
namespace [[deprecated]] useful_stuff { // ... }
  • Namespace functions and variables may be deprecated individually like any other variable/function, as shown above
Enumeration and enumerator values

Deprecate the whole enumeration;-

enum [[deprecated]] colours { RED, GREEN, BLUE };

Deprecate a single enumerator;-

enum colours { RED, GREEN [[deprecated]], BLUE };
Template specialisation
// Primary template template<typename T> class X { // ... }; // Specialisation template<> class [[deprecated]] X<int> { // ... };

[[likely]] and [[unlikely]]

The [[likely]] and [[unlikely]] attributes are used to indicate to the compiler execution paths that are more or less likely to be taken. This may allow the compiler to apply optimisations based on these directions

[[likely]] and [[unlikely]] cannot both be applied to the same label/statement

These attributes may be applied as follows. Note that this is not an exhaustive list of examples; these attributes may be applied to any label or statement other than a declaration (it may also be applied to function calls, but not function definitions)

In the following examples, [[likely]] and [[unlikely]] are interchangeable

if statement

Indication of which branches of an if statement are likely or unlikely paths of execution

if (a == 1) [[likely]] { // ... } else [[unlikely]] { // ... }
switch statement

Specify likely (or unlikely) case options; in this example, a == 2 is marked as being a likely execution path;-

[[likely]] switch (a) { // ... }
switch (a) { case 1: // ... [[fallthrough]] [[likely]] case 2: // ... break; case 3: // ... break; [[unlikely]] default: // ... break; }

The [[likely]] and [[unlikely]] attributes are only considered for the specific label they are applied to. So in the above example, the [[likely]] attribute is only considered for the condition a == 2. The previous condition (where a == 1) does not inherit the attribute in any way, despite the [[fallthrough]]

for statement
for (a = 1; a < b; ++a) { [[likely]] // ... }

An indication that the body of the loop is likely to execute at least once

while statement
[[likely]] while (cond) { // ... }
do statement
[[likely]] do { // ... } while (cond);
break and continue statements
[[likely]] break;
[[unlikely]] continue;
try and catch statements
[[likely]] try { // ... } catch ([[unlikely]] Error err) { // ... }


The [[maybe_unused]] attribute is used to indicate that a declaration may not be used. If a compiler would normally issue a warning for an unused entity, then this attribute will suppress it

It may be applied to the same entities as [[deprecated]] (except for namespace names and template specialisations) in the same way


The [[nodiscard]] attribute is used to indicate that the return value of a function holds some significance and should not be ignored by the caller. It may be applied to a function declaration, a class declaration or an enum declaration

If the caller of a function with this attribute ignores the function's return value, then the compiler should issue a warning

Similarly, if a function returns a class or enum type that has been declared with this attribute then the compiler should issue a warning. For example;-

class [[nodiscard]] X { // ... }; enum [[nodiscard]] serious_error { pretty_bad, oh_dear, boom };


A Name refers any of the following;-

Naming Rules

Note: Although not actually Names, type names also follow these naming rules


A declaration (or a definition, if no previous declaration exists) introduces a name into a scope. Here is a list of possible scopes and details of what qualifies a name to be considered to be within each scope;-

LocalA name declared within a function or lambda, or as an argument to the function/lambda. The scope of the name extends from its point of declaration to the end of the enclosing block
ClassA ('member') name defined within a class, outside of any function, embedded class, enum, or namespace. The scope of the name extends to all parts of the class
NamespaceA ('namespace member') name defined within a namespace, outside of any function, lambda, class, enum, or other namespace. The scope of the name extends from the point of declaration to the end of the namespace, but may be made accessible to other translation units
GlobalA ('global') name defined outside of any function, lambda, class, enum, or namespace. The scope of the name extends from the point of declaration to the end of the file it is declared in, but may be made accessible to other translation units
StatementA 'local' name defined within the {} block of a for, while, if, or switch statement, or naked; with no preceding statement. The scope of the name extends from the point of declaration to the end of the enclosing statement block
FunctionA label defined within a function. Its scope extends from the start of the function to the end

Any name may be a type name or a non-type name. A type name is a struct, class, enum or union. A non-type name is a variable, function or an argument to a function

For the purposes of the following discussion, alias, typedef and template names do not feature (it's not possible to do so without causing an error)

Name Hiding And Qualification


A Namespace defines a named scope. This allows collections of logically related (type/function/object) names to be grouped together; they become members of the namespace. This notion allows the same names to exist in multiple parts of the codebase. Because they are in different scopes, the names do not interfere with each other. A namespace is declared like this;-

namespace namespace-name { …member declarations… }

Nested Namespaces

Namespaces may be nested (this is used in the standard library in the chrono and rel_ops classes);-

namespace my_namespace { // ... namespace my_sub_namespace { // Declaration void sub_fn(); } } // Use sub_fn() my_namespace::my_sub_namespace::sub_fn();

Nested namespaces may be specified directly. For example, declaring sub_fn() from the previous example could also be written;-

namespace my_namespace::my_sub_namespace { void sub_fn(); }

Inline Namespaces

Any members within a namespace declared as inline will take on the scope of the including namespace. For example;-

// File: ver_3.h namespace ver3 { void fn(); }

// File: ver_4.h inline namespace ver4 { void fn(); // Improved version of ver3::fn() void fn2(); // New feature }

namespace my_library { #include "ver_3.h" // Because namespace 'ver4' is inline, the scope of all its members behave as if declared // directly at this point here (within namespace 'my_library') #include "ver_4.h" } // Use the default versions of fn() and fn2() - ie, my_library::ver4::fn() and // my_library::ver4::fn2() my_library::fn(); my_library::fn2(); // Use a specific version of fn() and fn2() my_library::ver3::fn(); my_library::ver4::fn(); my_library::ver4::fn2();

Unnamed (Anonymous) Namespaces

A namespace can be created without a name such as;-

namespace { // ...definitions... }

Don't use unnamed namespaces within header files; their implicit scope mechanism can cause problems

Namespace Hierarchies

Namespaces can include other namespaces. This can lead to naming conflicts. using-declarations, using-directives and namespace-alias' can resolve these issues and are described in the following sections

There is often a trade-off when using (or not using) using-declarations and using-directives; a trade-off between convenience, verbosity and clarity (of where a referenced object comes from). This must be dealt-with on a case-by-case basis

Generally, if references to many names from the same namespace are being made, then a using-directive may be appropriate. If there are multiple references to only a single (or a few) members of a particular namespace then a using-declaration is probably more appropriate. For infrequent references to individual names, explicit qualification is probably better

A number of using-declarations provides much finer-grained control than a single using-directive

The using qualifier should be restricted to small scopes to avoid confusion and accidental misuse. Overuse can also cause the very name clashes that namespaces are intended to avoid

'using' Declaration

If a namespace-scoped name is used often, a synonym (a using-declaration) may be defined via the using qualifier. This eliminates the need to constantly explicitly qualify the name with ::. Rather than this;-

std::string s;

…it is possible to do this;-

using std::string; // From this point on, any unqualified use of 'string' will refer to std::string... string s;

'using' Directive

A using-directive may be used to bring into scope all members of a namespace. For example;-

// Bring in the whole standard library namespace using namespace std; // From this point on, we can refer to any member of std:: without qualification... string s; vector v;

Using 'using'

Both using-declarations and using-directives may be used within other namespaces. Apart from bringing-in common external namespaces, this can be useful if a hierarchy of namespaces are being defined, such as a 'user' part and an 'implementation' part

The technique can also be used to construct local collections of other namespaces. For example;-

namespace my_library { using external_lib1; using external_lib2; using external_lib2::Z; using void external_lib3::fn1(); using external_lib3::X; }

Namespace Alias'

Program Behaviour

Undefined Behaviour

The C++ language entertains the concept of Undefined Behaviour. Essentially, this arises from code structures, techniques and/or control paths that do not perform in a predictable way; often without any warning from the compiler

Avoid any and all operations that may lead to Undefined Behaviour. Even if the operation appears to work correctly, it should be avoided, even if the alternative (that does not exhibit Undefined Behaviour) is more expensive

Most cases of Undefined Behaviour will not elicit any warnings from the compiler

Unspecified Behaviour

Some behaviour is left to the discretion of the implementation, though it must be handled 'correctly' by the implementation

Implementation-Specific & Implementation-Defined Behaviour

Some behaviour is left to the discretion of the implementation, but it should be documented by the implementation sufficiently well enough to be able to make meaningful analysis of the behaviour

Fundamental Data Types

TypeFamilyGuaranteed Minimum Data Size (bits)
charCharacter8 - may be signed or unsigned
signed charCharacter8 - signed
unsigned charCharacter8 - unsigned
wchar_tCharacterImplementation-specific but ≤ sizeof(long)
char8_t Character8 (for UTF-8). Has the same size/alignment/sign as unsigned char
char16_tCharacter16 (for UTF-16). Has the same size/alignment/sign as std::uint_least16_t
char32_tCharacter32 (for UTF-32). Has the same size/alignment/sign as std::uint_least32_t
short intIntegral16 - signed by default
intIntegral16 (usually native arch. size) - signed by default
unsignedIntegralas int but unsigned
long intIntegral32 - signed by default
long long intIntegral64 - signed by default
floatFloating Point32
doubleFloating Point64
long doubleFloating Point64 (see note below)
boolBooleanImplementation-specific but ≤ sizeof(long)
voidno dataImplementation-specific - an incomplete type

int vs unsigned int

Whether to prefer (signed) int or unsigned int types can invoke passionate argument. In theory, there is no reason why one should provide better performance than the other

However, there is one crucial difference that can and does provide a performance advantage for one. That difference is that the behaviour of arithmetic underflow and overflow are undefined for signed integer types, whereas it is very well defined for unsigned integer types

An implementation can use this undefined behaviour for signed types to make certain optimisations; essentially, the compiler can ignore the possibility of underflow and overflow because such behaviour is undefined anyway and therefore can never (legally) happen. Consider the following;-

void fn(int max) { for (int a = 0; a <= max; ++a) { /* ... */ } }

In this example, the compiler can always assume that a will never overflow and so the loop will execute exactly max + 1 times. The compiler can use this assumption to make certain optimisations

If the type of max and a were both changed to unsigned int types then this assumption no longer holds true; if max were set to std::numeric_limits<unsigned int>::max() then the loop would run indefinitely because a would eventually overflow and wrap-round back to zero. This possibility introduces uncertainty and so prevents the compiler from implementing the same optimisations it could for the signed version

Essentially, the lack of well-defined underflow and overflow for signed integer types allows the compiler to be lazy in certain situations, and this laziness can lead to improvements in the generated code

Operations on signed integer types can also sometimes be faster because some processors devote more resources (literally, more transistors) to signed operations than unsigned operations. Any such differences are likely to be minute but nonetheless, they do exist

None of the above should be taken as an argument against using unsigned integer types, but if performance is absolutely imperative then the coder should be aware of the issues. In reality, improving an algorithm or restructuring how critical operations are performed are likely to yield orders of magnitude greater performance benefits than changing unsigned types to signed types

Fundamental Type Modifiers

The following modifiers may be applied to char, short, int, long and long long types

const volatile signed type varnameSigned
const volatile unsigned type varnameUnsigned

Regular Types

Regular type is a loose term but generally refers to a type that;-

Literal Types

A user-defined type can be used as a constant expression if it is sufficiently simple. 'Sufficiently simple' means the constructor must have an empty body and all members must be able to be initialised with constant expressions. Such a type is called a Literal Type

Here is an example of a literal type being used by several constant expressions;-

struct vector { int x, y, z; constexpr int calc(int q) const { return (q * (x + y + z)); } }; // Use the vector type in a constant expression... constexpr vector a {1, 2, 3}; constexpr int b = a.z; constexpr vector c[] = {a, vector{4, 5, 6}, vector{8, 7, 6}}; constexpr int d = c[1].y; // d == 5 constexpr int e = a.calc(d); // e == 30

The use of constexpr for the function calc() in the above example implies const and therefore the latter should not have to be specified. However, experience has shown that this is not always the case; the 'Clang/LLVM' compiler complains if const is omitted; not because it is wrong but because if the function were also used at run-time, it could behave differently

Trivial Types

A trivial type is one that has standard copy semantics; ie, it must be trivially copyable and movable. It must also have a trivial destructor. A copy/move/destructor operation is trivial if;-

The standard library predicate is_trivial<T> may be used to test the above rules

Standard Layout Types

A standard layout type is one that;-

Basically, if a type can be expressed in plain 'C' then it is probably a standard layout

The standard library predicate is_standard_layout<T> may be used to test the above rules

Aggregate Types

An aggregate type is an array (even an array of non-aggregate types) or a class (often a struct or a union) that has the following properties;-

An aggregate type may be aggregate initialised

An aggregate type may be copy list initialised

Miscellaneous Types

Defined in <cstddef> (note that the types defined in the std namespace are always the same as the 'C' equivalents; both are shown below where applicable);-


Implements the concept of a byte in memory. It is the same size as a char but is not a character type. Only bitwise logical operations are defined for it (no arithmetic)

Implemented as an empty enum with an underlying type of unsigned char

Explicit casts must be used to convert to/from std::byte; an integral type can be converted to a byte by using std::byte{n}, and a std::byte can be converted to an integral with std::to_integer<T>(std::byte b). For example, int a = std::to_integer<int>(b);

See also this point


An unsigned integer large enough to hold the size (in bytes) of any other type. It is returned from sizeof(), alignof() and offsetof()

size_t is a good choice for an array index as it is guaranteed to be large enough for any possible index value

Note that there is no std::ssize_t to match the 'C' (signed) ssize_t

A trivial type whose alignment requirement is at least as strict/large as any other scalar type. In practice this means that its alignment is often that of long double which is the largest scalar type
A signed integer large enough to hold the result of any pointer subtraction

The type of the literal nullptr. This is a distinct type and is not actually a pointer type

std::is_null_pointer<T>::value may be used to test if type T is a nullptr_t type. Defined in <type_traits>

Defined in <cstdint>;-


Signed and unsigned integer types large enough to hold a pointer value. These can be readily copied to/from void* without casting. Unlike a void*, these types support arithmetic and logical operations. These types are most useful for memory management applications

The min and max values of intptr_t are indicated by the macros INTPTR_MIN and INTPTR_MAX respectively. the max value of uintptr_t is indicated by the macro UINTPTR_MAX with the min value always being zero

Because a char is not guaranteed to be 8 bits, adding 1 to a char* is not guaranteed to actually increment it by 1. In contrast, adding 1 to a intptr_t or uintptr_t will always do precisely that

An implementation may opt to not define these types


Signed integer types of exactly the number of bits indicated

Min and max values are indicated by the macros INTn_MIN and INTn_MAX respectively for each data size

Negative values are implemented as 2's complement

An implementation may opt to not define these types, and may not be able to if the underlying architecture does not directly support them


Unsigned integer types of exactly the number of bits indicated

Max values are indicated by the macros UINTn_MAX for each data size. Min value is always zero

An implementation may opt to not define these types, and may not be able to if the underlying architecture does not directly support them


Fastest signed integer types of at least the number of bits indicated

Min and max values are indicated by the macros INT_FASTn_MIN and INT_FASTn_MAX respectively for each data size


Fastest unsigned integer types of at least the number of bits indicated

Max values are indicated by the macros UINT_FASTn_MAX for each data size. Min value is always zero


Smallest signed integer types of at least the number of bits indicated

Min and max values are indicated by the macros INT_LEASTn_MIN and INT_LEASTn_MAX respectively for each data size


Smallest unsigned integer types of at least the number of bits indicated

Max values are indicated by the macros UINT_LEASTn_MAX for each data size. Min value is always zero


Maximum sized signed integer type supported by the implementation

Min and max values are indicated by the macros INTMAX_MIN and INTMAX_MAX respectively


Maximum sized unsigned integer type supported by the implementation

Max value is indicated by the macro UINTMAX_MAX. Min value is always zero

auto This is not actually a type at all (it is a keyword) but it is used in place of a type name. A variable of type auto must be initialised at definition; the actual type of the variable is selected by the compiler to something appropriate based on the type of the variable or literal that is assigned to it

Incomplete Types

The following are considered incomplete types

In general terms, an incomplete type may not be used if the type's layout or size is required

In particular, a pointer to an incomplete type can be used, as long as it is not dereferenced. For example, the following is legal;-

class X; void fn2(X* a); // This function successfully uses the incomplete type 'X*' // because it does not need to know its layout or size void fn(X* a) { fn2(a); }

User-defined Data Types

Declarator Operators

type* var-namePointer
type** var-namePointer to a pointer
type* const var-nameConstant pointer
type* volatile var-nameVolatile pointer
type& var-namelvalue reference (must be initialised)
type&& var-namervalue reference (must be initialised)
type var-name[]Array
type fn-name(args)Function
auto fn-name(args) -> typeTwo operators; auto indicating a function with a suffix return-type and -> indicating the return type
auto fn-name(args) Function with deduced return type

Derived Types

type* var-name;Pointer
type var-name[n];Array
type& var-name = initialiser;lvalue reference (must be initialised)
type&& var-name = initialiser;rvalue reference (must be initialised)
struct type-name {};Structure
union type-name {};Union
enum {} type-name;"Plain" Enumeration. Size is implementation-defined
enum class type-name {};Class enumeration. Size is implementation-defined
class type-name {};Class

Deferred Type Definitions

If a type is required but that type is not yet defined, a Forward Declaration may be specified. For example;-

struct T1; // Only declared; defined later struct T2 {T1* a, T1* b} // Ok: Use T1 before it is defined void fn(T1 a); // Ok: Declaration of a function that takes a T1 type struct T1 c; // Error: T1 is not defined // ...at some point later... struct T1 {int y, int z}; // Define T1 struct T1 d; // Ok

Deducing Types

There are several sets of rules for type deduction; used in a variety of scenarios;-

Manually determining a deduced type can become very difficult and complex. One method of getting the compiler to output a type is to deliberately cause an error. The following definition will do this;-

// Declare a template with no definition template<typename T> class TD;

The above could be used like this;-

template<typename U> void fn(U& a) { // The following will cause two errors to be generated // that should include the types for 'U' and 'a' TD<U> u_type; TD<decltype(a)> a_type; } // Similarly for determining the type of an 'auto' auto& c = x; TD<decltype(c)> c_type;

Don't use std::type_info, ie typeid(T).name(); it almost always gives the wrong answer! The reason for this is that std::type_info::name is specified to return a type as if the argument to type_info had been passed by value to a function template. This makes it unreliable because the function template type deduction rules strip references, const and volatile from such arguments. If you choose to ignore this advice, then note that some compilers provide a c++filt command that can interpret and present the name and type information returned from type_info::name

The Boost library provides type_index.hpp which defines type_id_with_cvr<> which can be used to retrieve run-time type information;-

using boost::typeindex::type_id_with_cvr; cout << "Type = " << type_id_with_cvr<T>().pretty_name() << endl;

Function Template Type Deduction

Type Deduction Rules

When a function template is called, two type deductions take place; one for the type T and one for the function argument(s) based on T. How this is performed depends on the function argument declaration(s) and how the function is called. Consider;-

template<T> void fn(arg-type a);

The above could be called with;-


The form of expr and arg-type interact to deduce the type of T and the type arg-type

Note #1 Because fn() takes a reference, the array type passed to it does not decay to a pointer. This technique can be used (for example) to create a template that returns the number of elements in an array;-

template<typename T, std::size_t N> constexpr std::size_t array_size(T (&)[N]) noexcept { return N; } // Use array_size() int x[] = {1, 2, 3, 4, 5}; int y[array_size(x)]; // Same number of elements as x[]

Note #2 Because fn() takes a reference, the function type passed to it does not decay to a pointer

Note #3 The derived type of T is always non-const even if a const value is supplied in the call. This is because the const-ness is taken care of, and guaranteed by the function declaration itself

auto Object Types

auto varname = expr; deduces an object's type from its initialiser. The type may be a variable type, a const or constexpr

Assignment to an auto type implies type deduction only; there is no implicit type conversion performed. This can improve performance and reduce errors by preventing unintentional type conversions

Make extensive use of auto and prefer it to explicit types;-

Using auto with specific types

The following two expressions are (almost; see below) equivalent;-

int a = 42; auto a = int{42};

However, the latter is the preferred style (called auto to stick). The reasons are that it still provides the advantages of using auto with no loss of control

Note that this preferred style implies a copy (actually, a move) operation; the expression initialises the object and then the object is moved to the new auto variable. In practice, this move is almost always optimised away by the compiler. This point is important in understanding the following situations where this preferred style cannot (or should not) be used;-

auto a = std::atomic<int>{}; // Error: std::atomic is not movable 1 auto b = std::array<int, 1000>{}; // Warning: This will work, but the array move could be very expensive auto c = make_derived_t(); // Ok, but if 'c' is supposed to be a 'base' reference, then possibly // would be better expressed as 'base_t* c = make_derived_t(); or // (better) 'auto c = unique_ptr<base_t>{make_derived_t()};'

1 If the type is non-moveable, then using auto will fail. Move-elision is guaranteed when declaring auto objects in this way; hence none-movable types can be used. See also Return Value Mechanics

If the type is expensive to move, then using auto could be (potentially) inefficient

auto Type Deduction

The rules for auto type deduction are exactly as for function template type deduction, ( with one exception). With reference to the simple function template;-

template<T> void fn(arg-type a); // fn() may be called with... fn(expr);

…think of an auto expression as taking the form;-

auto a = expr;

Where auto takes the part of T from the template, and the deduced type of the variable a takes the part of arg-type

As with function template type deduction, there are three cases;-

The one difference between template and auto type deduction comes about from the use of uniform initialisers (which get interpreted as std::initialiser_list constructs);-

Non-auto caseauto equivalentDeduced type of auto case
int b = 42;auto a = 42;int
int b(42);auto a(42);int
int b = {42};auto a = {42};std::initializer_list<int> containing the single int element of value 42
int b{42};auto a{42};std::initializer_list<int> containing the single int element of value 42
  b = {42, 83, 11};
auto a = {42, 83, 11};std::initializer_list<int> containing the three int elements of values 42, 83, 11
  b{42, 83, 11};
auto a{42, 83, 11};std::initializer_list<int> containing the three int elements of values 42, 83, 11

auto never deduces to a std::initialiser_list type. The above examples therefore give the following results;-

Non-auto caseauto equivalentDeduced type of auto case
int b = 42;auto a = 42;int
int b(42);auto a(42);int
int b = {42};auto a = {42};int
int b{42};auto a{42};int
  b = {42, 83, 11};
auto a = {42, 83, 11};Error
  b{42, 83, 11};
auto a{42, 83, 11};Error

When auto Deduces The Wrong Type

auto sometimes deduces a type other than one expects. A common example is when proxy classes are being used; classes that emulate some other type. For example;-

std::vector<bool> a; bool b = a[3]; // 'b' is a bool and contains the status of bit 3 from the vector auto c = a[3]; // Error: 'c' is of type std::vector<bool>::reference and may exhibit undefined behaviour

The above problem comes about because vector defines a specialisation for bool; packing single bit bool values into words. As a result, vector<bool>::operator[]() returns a proxy class to hide this fact and to provide a clean interface to the resulting expression, with the primary intention of making vector<bool>::operator[]() look like it returns a T& in the same way as the general vector<T>::operator[]() operation does

Such a proxy class is intended to be transparent and usually only used an an rvalue. In the above case, the type std::vector<bool>::reference probably contains an internal pointer which, if a were an rvalue (say, returned from a function), would be left dangling when assigned to c. Other proxy classes include 'smart' pointers std::unique_ptr and std::shared_ptr (though these are designed to be more visible), std::bitset (similar issue to vector<boot>), and many others.

In cases such as these, options include:-

Lambda Capture Type Deduction

There are three types of lambda capture;-

Normally, the difference between the way by-value and init-capture handle const (and volatile) is not important because the object that a lambda creates is const anyway. However, if the lambda is made mutable then the difference does become apparent


decltype(expr) deduces a type from an expression; that is, the declared type

decltype Type Deduction

Givendecltype ExpressionYields Deduced Type
int a = 0;decltype(a)int
const int b = 0;decltype(b)const int
const int& c = b;decltype(c)const int&
X d;
char fn(const X& e);
decltype(fn)char(const X&)

Function Return Type Deduction

See function return type deduction


TypeFamilyImplied Type
nIntegralint sign extended
nL nlIntegrallong sign extended
nLL nllIntegrallong long sign extended
nU nuIntegralunsigned
nUL nul nLU nulIntegralunsigned long
nULL null nLLU nlluIntegralunsigned long long
0Bb 0bbIntegralint binary notation
0oIntegralint octal notation
0Xh 0xhIntegralint hexadecimal notation
n. n.n .n
nEn n.En n.nEn .nEn
nE+n n.E+n n.nE+n .nE+n
nE-n n.E-n n.nE-n .nE-n
Floating Pointdouble
0xhPn 0xh.Pn 0xh.hPn 0x.hPn
0xhP+n 0xh.P+n 0xh.hP+n 0x.hP+n
0xhP-n 0xh.P-n 0xh.hP-n 0x.hP-n
Floating Pointdouble
n.nL n.nl (etc. for other forms)Floating Pointlong double
n.nF n.nf (etc. for other forms)Floating Pointfloat
true / falseBooleanbool
'c'Characterchar (int in 'C')
"c…"Stringchar[n] where 'n' is the length of the string + 1 (for the NULL terminator)

Character And String Literals

The following 'escape' (\) codes may be used in characters or strings

NameASCII NameC++ Name
NewlineNL (LF)\n
Horizontal tabHT\t
Vertical tabVT\v
Carriage returnCR\r
Form feedFF\f
AlertBEL or alert\aEmits a sound on some consoles
Question mark?\?Can be useful for avoiding confusion with trigraph characters
Single quote'\'
Double quote"\"
Decimal numbernnn\nnnMust start with a non-zero digit
Octal numberooo\0oooMust start with a zero
Hexadecimal numberhh\xhh
char'c'Plain char. Almost always ASCII
wchar_tL'c'Wide character. Implementation-specific
-u'c' UTF-8 character. The type that this yields is implementation specific but will be one of the wider character types (likely wchar_t or char16_t)
char16_tu'\Uhhhh' u'\uhhhh' u'\xhhhh'UTF-16 character. Expands to 0000hhhh (16 bit Hex) Unicode Code-Point
char32_tU'\Uhhhhhhhh' U'\uhhhhhhhh'
UTF-32 character. 32 bit Hex Unicode Code-Point
const char string"c…"Defines const char[n] where 'n' is the length of the string + 1 (for the NULL terminator)
Raw const char stringR"(c…)"A char string but the normal escaped '\' characters are not interpreted. Useful for creating regex
const wchar_t stringL"c…"Wide character string. Terminated with L'\0'
LR"(ccc…)"Raw wide character string. Terminated with L'\0'
UTF-8 const char string 
UTF-8 const char8_t string 
u8"c…"UTF-8 string. Terminated with '\0'
u8R"(c…)"Raw UTF-8 string. Terminated with '\0'
UTF-16 const char16_t stringu"c…"UTF-16 string. Terminated with u'\0'
uR"(c…)"Raw UTF-16 string. Terminated with u'\0'
UTF-32 const char32_t stringU"c…"UTF-32 string. Terminated with U'\0'
UR"(c…)"Raw UTF-32 string. Terminated with U'\0'

String Literals

const char* p = "Hello";Assign a string literal to a const pointer
const char16_t* p = uR"(Hello)";Assign a (raw) UTF-16 string literal to a const pointer
char* p = "Hello";Illegal; not a const pointer
char p[] = {"Hello"};Assign to an array. Receiving array does not have to be const and (in this case) is automatically sized to that of the string + 1 (for the NULL terminator); ie, 6. Note that the {} are optional
char p[] = {"Goodbye " "Cruel " "World"};A string literal may be composed of one or more sub-strings (automatically) concatenated together; useful for specifying very long strings. In this example, the resulting string shall be 18 characters + 1 (for the NULL terminator)

The namespace std::literals::string_literals defines operators for defining std::string literals;-

auto s = "Hello"s;A std::string literal
auto s = L"Hello"s;A std::wstring literal
auto s = u"Hello"s;A std::u16string literal
auto s = U"Hello"s;A std::u32string literal

Type Size and Alignment

The following compile-time operators are available to determine the physical size and alignment of an object;-

std::size_t sizeof expression Returns the number of bytes occupied by the physical object representation resulting from expression. No implicit type conversions are performed
std::size_t sizeof(type) Where type cannot be a function type, an incomplete type, void, or a bitfield. The returned value is the number of bytes occupied by the physical representation of the type
std::size_t alignof(type)

The returned value is the alignment requirement of type, in bytes. If the type is a reference then the referenced type is assessed. If the type is an array then the array element type is assessed

See also max_align_t

alignas(type) Allows aligning of a variable to be the same as some other type. For example alignas(X) int data[42]; will set the alignment of the array data to be the same as that of type X
std::size_t offsetof(type, member)

The returned value is the number of bytes member is displaced from the beginning of type (which must be a struct or class type). Padding bytes within the type will, naturally, effect the result

If the type is not a standard layout then the result is undefined

The implementation of offsetof is optional

Example, given;-

struct X { char m1; int m2; };

offsetof(X, m1) will return 0 (zero), and (depending on the types' sizes and alignment requirements), offsetof(X, m2) will (probably) return 4 or 8

Variable Declarations and Definitions

lvalues And rvalues

Every expression is either an lvalue or an rvalue, but not both

objectA contiguous region of memory/storage (this is a low level definition and not to be confused with class objects)
lvalueA name or expression that refers to an 'object'. An lvalue has an identity and cannot be moved. Generally, an lvalue is a name for which the address may be taken. It is a glvalue that is not also an xvalue

A "value that is not an lvalue"; often ( though not always; see below) a temporary object created as an artefact of an expression or returned from a function

An rvalue can be moved/copied (assigned to an lvalue). It is not possible to take the address of an rvalue

glvalueA "generalised lvalue" is an lvalue that has identity
xvalueAn "expiring lvalue" is a glvalue whos representation can be re-used. It has identity and can be moved
prvalueA "pure rvalue" has no identity but can be moved

The value class (that is, lvalue, rvalue, etc) is a property of an expression; not of an object

Do not assume that all expressions involving temporary objects yield rvalues, or express some other similar relationship; the two concepts are entirely separate

Object Lifetime

TypeInitialisation and Lifetime
automatic(applicable only in function scope) Initialised on each encounter (if at all). Usually stack-based. Exist from definition to end of scope . The term 'automatic' is a legacy term and has now largely fallen out of use. It is not to be confused with the auto type
staticWhen used within a function scope, initialised once only at definition and exists until end of program execution
Free storeExist from the time of an explicit new operation until destroyed with delete
temporary objectintermediate result in computation, or an object for a const reference. the lifetime depends on context. these are almost always stack-based objects
thread-local objectan object declared thread_local. lifetime is that of the host thread

Storage Classes

Storage ClassEffect on variable
extern type varIndicates that var is defined elsewhere (possibly in some other translation unit), and this only a declaration. A variable declaration that includes an initial value is a definition regardless of the use of extern
static type var

If in file (global) scope, static prevents the variable from being accessed from an external file; ie, it creates an internal linkage. The variable shall be initialised prior to calling main(). See also this point

If the static variable is part of a class, it is common (shared) between all instances of that class. Like a file (global) scoped variable, it shall be initialised (once only) prior to calling main(). See also this point

If it is in function scope, a static variable retains its value between function invocations. It shall be initialised (once only) the first time the execution path passes through the variable definition; subsequent passes through the variable definition will ignore any initialising expression. Initialisation is undefined if performed recursively. For example;-

void fn(int a) { static int n = a; // Ok static int n = fn(a + 1); // Undefined return n; }
register type var

Hint to compiler to give speed priority to variable. Cannot be referred to by a pointer. Often ignored by modern compilers

register is deprecated

register is removed, though the keyword remains reserved


Variable is allocated on the stack and is destroyed at the end of the containing block's scope. Implicit when variable is defined within a function

The variable shall be initialised each time the execution path passes through the variable definition

The auto keyword could be used to explicitly identify an automatic variable

Such use is now deprecated.  …and now auto means something quite different!


Implicit when variable is allocated at file scope, outside of any function. The variable shall be initialised prior to calling main(). See also this point

thread_local type var Indicates that each thread is allocated its own copy of var


The volatile modifier may be applied to any object declaration. Some examples;-

volatile int a; // The int 'a' is volatile volatile int* b; // The int referred to by 'b' is volatile volatile X c; // The object c is volatile

The purpose of volatile is as an instruction to the compiler not to optimise-away what may seem like redundant read/write operations. For example;-

volatile int* p; // ... *p = 0; *p = 1;

In the above example, had p not been declared volatile then the compiler would likely optimise-away the line *p = 0; because it would "know" that it was redundant. The volatile modifier prevents such optimisation

Declarations and Definitions

All variables and functions must be declared or defined before they are referenced

char c;A variable of type char. Implicitly initialised to zero if in global scope, or static
char c, d, e;Three variables of type char. Implicitly initialised to zero if in global scope, or static
char c = 'B';A char, assigned a value of 'B'
int i = 123;An int, assigned a value of 123
int i = j;An int, initialised with the variable 'j'; the type of 'j' may or may not be the same; if it is not the same then some implicit conversion is required/expected
double f {3.14};A double, assigned a value of 3.14
const char* a = "Hello";A char pointer, assigned a (const) string value of "Hello"
const char* a[] = {"yes", "no"};An unsized array of char pointers, each assigned a separate (const) string value. The array is unsized; its actual size shall be determined by the number of values assigned
auto a = 123;A variable of type auto, assigned a value of 123. The actual type shall be selected at compiler time (in this example, it will probably be of type int)
X y {123, "Hello", true};A user-defined type X, initialised with three values; an Integral, a const string, and a bool
int stop() {…}A function definition. The function takes no parameters and returns an int
bool go(const colour_t colour);A function declaration. The function takes a single parameter of type colour_t and returns a bool
using T = int;An alias for type int

There is rarely a need to introduce a new variable before there is a value available for it; reducing variable scope and delaying name introduction within a scope can help minimise errors and reduces namespace pollution

This is essentially the RAII (Resource Acquisition Is Initialisation) principle; this improves performance as no default initialiser is called prior to assigning a 'real' value. The most common reason for NOT initialising a value to something other than its default at definition is that it needs to be passed to a function to initialise it

One very neat example of employing the principles of limiting a variable's scope and delaying declaration until we have a value to initialise it with is to define the definition within an if statement;-

if (int a = fn()) { // We are free to manipulate 'a' but its scope is limited to this block } else { // The scope of 'a' extends to here too }

Only a single declaration may be made in this way, and it must be initialised (which is implied by the fact that the if statement operates on an expression)

A similar technique may be used with for

Inline Variables

A variable may be defined as inline. For example;-

inline int a{42};


An object may be variable (mutable) or constant (immutable). The former is the default. The latter can be achieved with the const specifier. The general form is;-

const DataType VarName = n; // 'VarName' is a constant value 'n'

Some examples;-

const int life = 42;A constant int
const char* p = "Hello";A pointer to a constant
char* const p = "Hello";A constant pointer
const char* const p = "Hello";A constant pointer to a constant


A type alias generates a new name for an existing type. The new and old names are interchangeable; they are not distinctly different types. Aliases are useful to insulate the code from the underlying type details and allow for simpler modification. Not to be confused with object/variable aliasing

There are two methods of creating a type alias;-

typedef existing_type new_type;'C'-style
using new_type = existing_type; 'C++'-style

Every storage class in the standard library defines value_type which is a synonym for the type it has been instantiated with. For example;-

template<class T> class vector { using value_type = T; // ...etc... }

Structured Binding Declaration

Structured bindings provide a method of decomposing a complex object into individual variables. The syntax is as follows;-

[[attributes]] const volatile auto&& [ identifier-list ] = expression; [[attributes]] const volatile auto&& [ identifier-list ] {expression}; [[attributes]] const volatile auto&& [ identifier-list ] (expression);

Note: The process of constructing the variables defined by identifier-list starts with the creation of a temporary object to hold the initialising values. It is to this temporary object that the [[attributes]] const volatile applies, and not to the identifier-list variables

Array binding

The number of names specified by identifier-list must match the size of the array. Each name is then bound to each array element in-turn

int a[3] = {7, 11, 13}; // Create copies of the array elements // Name Type Value // x int 7 // y int 11 // z int 13 auto [x, y, z] {a}; // Create references to the array elements // Name Type Value // r int& ref's a[0] // s int& ref's a[1] // t int& ref's a[2] auto& [r, s, t] {a};

struct and class binding

The struct/class must either contain only public non-static members, or no non-static members at all, but one or more bases, only one of which (which must be public) may contain non-static members, all of which must be public

The struct/class and/or its bases may contain static members

The number of names specified by identifier-list must match the number of non-static members of the struct/class. Each name is then bound to each member in-turn (in the order they are defined in the struct/class)

struct X { int m1 {42}; double m2 {3.14}; bool m3 {true}; static const int m4 {83}; }; X b{}; // Create copies of the struct elements // Name Type Value // x int 42 // y double 3.14 // z bool true auto [x, y, z] {b}; // Create references to the struct elements // Name Type Value // r int& ref. to b.m1 // s double& ref. to b.m2 // t bool& ref. to b.m3 auto& [r, s, t] {b};

Expanding on this example, showing a different (and common) context;-

// Derived from 'X' but contains no non-static members of its own struct Y : X { static const int m5 {83}; }; Y fn(); // Create copies of the struct elements returned from a function // Name Type Value // m int 42 // n double 3.14 // p bool true // (assuming the same default values shown above) auto [m, n, p] = fn();

If a class/struct does not meet the criteria for structured binding, it can be modified to make it so. See tuple binding for details of how this works. For example;-

// The private members of this class can't be automatically // used in a structured binding class Z { private: std::string m1; bool m2; std::vector<int> m3; };

To address this, first create a specialisation of a tuple_size for type Z. There are 3 members we wish to consider;-

namespace std { template<> struct tuple_size<Z> : std::integral_constant<std::size_t, 3> {}; }

Next, add a get() function to Z. This could be added as a member function (as shown here) or as a friend function;-

class Z { //...as previous... public: template <std::size_t N> constexpr decltype(auto) get() const { if constexpr (N == 0) return std::string_view{m1}; else if constexpr (N == 1) return m2; else if constexpr (N == 2) return (m3); // Note: (...) needed to preserve reference } };

Lastly, create a specialisation of a tuple_element for type Z. This uses the get() function already defined. This specialisation is independent of the number or type of members of Z;-

namespace std { template<std::size_t N> struct tuple_element<N, Z> { using type = decltype(std::declval<Z>().get<N>()); }; }

An easier to understand (but less maintainable) version of the above would be;-

namespace std { template<> struct tuple_element<0, Z> { using type = std::basic_string_view<char>; }; template<> struct tuple_element<1, Z> { using type = bool; }; template<> struct tuple_element<2, Z> { using type = const std::vector<int>&; }; }

With all the above in place, it is now possible to use structured binding on type Z;-

Z fn(); // Name Type // x std::basic_string_view<char> // y bool // z const std::vector<int>& auto [x, y, z] = fn();

tuple binding

The number of names specified by identifier-list must match the size of the tuple. Each name is then bound to each tuple element in-turn (in the order they are defined in the tuple)

int a{}; double b{}; bool c{}; std::tuple<int&, double&& bool> t(a, std::move(b), c); // Create references to the tuple elements // Name Type Value // x int& ref. to a // y double&& ref. to b // z const bool true const auto& [x, y, z] = t;

Further example;-

std::tuple<int, int&> fn(); // Name Type // x int // y int& auto [x, y] = fn(); // Name Type // x const int // y int& const auto [v, w] = fn();


C++ initialisation is notoriously complex; there are several forms, each with its own set of rules and caveats, and these are detailed below

However, in most normal use cases, the fine detail can be largely ignored (or at least not worried about too much). The point being, don't get bogged-down in the detail; in many cases, as long as the code is sensible, initialisation will behave sensibly. Mostly… maybe 

In the following text, the term initialisation is synonymous with construction

Initialisation Forms

The forms of initialisation are Zero, Constant, Default, Value, Direct, Copy (or Move), List (two forms), Aggregate, and Unspecified

An object may be subject to none, one or more of the above forms

Zero Initialisation

If an object meets any of the following criteria then it is zero initialised;-

static T name;An object of type T that is declared static or thread_local ( unless it is constant initialised instead). In this case, zero initialisation is performed before all other initialisation
T name = {};
For a non-class object undergoing value initialisation, or a member of a class object undergoing value initialisation, that has no constructors and is not being aggregate initialised (or is not specified in any aggregate initialisation)
T name[n] = "";An array of any character type that is initialised with a string literal will have all remaining space in the array (if any) zero initialised

Zero initialisation is never applied to a reference type

For an object of (possibly const/volatile) type T, zero initialisation will perform the following;-

Constant Initialisation

If a reference or object of type T is declared static or thread_local and it meets one of the following criteria then it is constant initialised;-

Default Initialisation

If an object meets any of the following criteria then it is default initialised;-

For an object of (possibly const/volatile) type T, default initialisation will perform the following;-

Value Initialisation

If an object of type T is defined with an explicit empty initialiser, for example T a{};, T a = {};, or T a(); (though this last form will be parsed as a function declaration in most cases) and it meets one of the following criteria then it is value initialised;-

The object is a nameless temporary
new T()
new T{}
The object is dynamically allocated with new
X::X(args) : membername() {...}The object is a non-static data member, or a base class
T name{}The object is a (possibly static or thread_local) named variable initialised with empty braces

The following exceptions apply;-

For an object of (possibly const/volatile) type T, value initialisation will perform the following;-

Direct Initialisation

If an object of type T is defined with a non-default (non-empty) initialiser, for example T a{args};, T a(args);, or T* a = new T{args};, and it meets one of the following criteria then it is direct initialised;-

T name(args);The (args) (parenthesis) format is used
Initialisation of a prvalue temporary by functional cast, with a parenthesised expression list, or with a static_cast expression
T name{args};The object is a non-class type and the {args} (braced-init) format is used. For class types, list initialisation will be performed instead
new T(args)Non-empty initialisation of an object allocated with a new expression
X::X() : membername(args) {...}Initialisation of a base or non-static member from constructor initializer_list
[arg](){...}The object is a lambda expression capture variable (which will initialise the lambda's closure object)

For an object of (possibly const/volatile) type T, direct initialisation will perform the following;-

The above is more relaxed than for copy initialisation; the latter only considers non-explicit constructors and conversion functions as valid candidates for selection, whereas direct initialisation considers all options

Copy (or Move) Initialisation

This occurs when T is a (possibly const/volatile) type and it defines a non-default constructor that may be called with the arguments specified by the initialisation T a = initialiser;

This is an initialisation and conversion operation. If the initialiser does not exactly match the object's type, then methods shall be sought to convert the initialiser to the correct type and ADNL shall be employed to determine if the initialiser can be used to construct the object

Given the initialiser X a = b;, b is converted to a type X and the result passed to a copy constructor of X (or if b is an rvalue, possibly a move constructor). Both explicit and non-explicit constructors are considered for ADNL purposes, but only non-explicit versions may be selected. In practice, the copy/move is usually optimised-away by the compiler. The effect of this behaviour when compared with direct initialisation can be seen here

The optimisation that can elide the call to the constructor is mandatory, even if invoking such a constructor would have side-effects. As a result, explicit constructors may be selected. Technically, the rule regarding not selecting explicit constructors still applies, but as the constructor is guaranteed to never be called, the rule never comes into play. See also Return Value Mechanics

List Initialisation

There are two forms of List Initialisation; Direct and Copy. These are described in the following sections. See also Lists

Direct List Initialisation

This occurs when T is a (possibly const/volatile) type and it defines a non-default constructor that may be called with the arguments specified by an initialisation of one of the following forms;-

T name {args};Initialisation of a named variable with a braced-init-list without specifying =
T {args}Initialisation of an unnamed temporary with a braced-init-list
new T {args}Initialisation of a dynamically allocated object with a braced-init-list
classname { T membername {args}; };Initialisation of a non-static data member without specifying =
classname::classname(args) : membername {args}
Initialisation via a member initialiser list with a braced-init-list

Both explicit and non-explicit constructors are considered and Argument Dependent Name Lookup is employed to identify the best candidate, and (if required) implicit conversion is used to modify the argument(s) to match the constructor (unless the constructor is explicit)

Copy List Initialisation

This occurs when T is a (possibly const/volatile) type and it defines a non-default constructor that may be called with the arguments specified by an initialisation of one of the following forms;-

T name = {args};Initialisation of a named variable with a braced-init-list and specifying =
fn({args})Function call where braced-init-list is used to initialise an object to be passed as the function's argument
return {args};A return statement specifying a braced-init-list used to initialise the returned object
name[{args}]A subscript expression where a braced-init-list is used to initialise a user-defined operator[] function's argument
name = {args}An assignment expression where a braced-init-list is used to initialise a user-defined operator= function's argument
T({args})A function-style cast or other constructor invocation where a braced-init-list is used to initialise the constructor function's argument (which in the case of a function-style cast is then cast by the function to a type T)
classname { T membername = {args}; };Initialisation of a non-static data member and specifying =

This behaves similarly to direct list initialisation except that explicit constructors are considered for ADNL purposes but never used; ADNL selection of an explicit constructor is an error

List Initialisation Effects

For an object of (possibly const/volatile) type T, direct list initialisation will perform the following;-

There are some restrictions placed on the forms of implicit type conversions allowed when performing list initialisation. The following implicit conversions are not allowed;-

Aggregate Initialisation

An aggregate type is eligible for aggregate initialisation

Unspecified Initialisation

Default initialisation may be specified as an empty list. For a type X, this could be X a{};. It is also possible to omit even the default initialiser; eg, X a;

When no initialiser is specified at all like this, there are special rules concerning the initialisation;-

For example;-

int* a = new int; // *a will be uninitialised int* b = new int{}; // *a will be initialised to zero class X { int a, int b; }; X* c = new X; // c.a and c.b will both be initialised to zero

Be safe by always explicitly initialising objects unless there is a specific reason not to; a classic case being a read/write buffer

User-Provided Constructors And Initialisation

An example of how the declaration (or not) of user-provided constructors can effect initialisation;-

struct X { int m1; X() = default; }; struct Y { int m2; Y(); }; Y::Y() = default; X a{}; Y b{} // At this point, a.m1 == 0, and b.m2 == undefined!

The reason for the above result is as follows;-

The way to avoid the above problem is;-

For user-defined types, there can be differences between the behaviour of the different initialisation forms; it depends on the type's implementation

Example; given the following declarations;-

class X; class Y { public: Y(); // Default constructor 1 Y(int a); // Constructor 2 }; class X { public: X(int a); // Constructor 4 X(const X& rhs); // Copy constructor 5 X(X&& rhs); // Move constructor 6 X(const Y& rhs); // Copy constructor 7 X(Y&& rhs); // Move constructor 8 X& operator=(const X& rhs); // Copy operator 9 X& operator=(X&& rhs); // Move operator 10 X& operator=(const Y& rhs); // Copy operator 11 X& operator=(Y&& rhs); // Move operator 12 };

…the following definitions will invoke the indicated constructor(s)/operation(s);-

X x{1}; // Calls 4 X a{x}; // These four definitions shall call 5 X b = {x}; X c = x; X d(x); // In principle, these four definitions shall call 4 and then take that // result to call 6 (or maybe 5). In practice, only the call to 4 is // likely to be made; the call to 6 (or 5) is optimised-away X e{X{3}}; X f = {X{3}}; X g = X{3}; X h(X{3}); Y v; // These two definitions shall call 1 Y w{}; Y x(); // This shall be parsed as a function declaration Y y{3}; // Calls 2 X j{y}; // These four definitions shall call 7 X k = {y}; X m = y; X n(y); // These four definitions shall call 2 and then take that result to call 8 X p{Y{3}}; X q = {Y{3}}; X r = Y{3}; X s(Y{3}); p = q; // Calls 9 p = X{3}; // Calls 4 and with the result, calls 10 p = y; // Calls 11 p = Y{3}; // Calls 2 and with the result, calls 12

{} vs () in initialisers

More complex and multi-element initialisation is achieved with an initialiser list

int a[]{1, 2, 3};Array initialisation. Array is implicitly sized to 3 elements
int a[42]{1, 2, 3};Array initialisation. Array is explicitly sized to 42 elements. The first three elements are initialised as specified and the remainder are initialised to zero
int a[42]{};Array initialisation. Array is explicitly sized to 42 elements and all elements are initialised to zero
struct S {int x, string s};
S s{1, "Hello"};
Structure initialisation
struct S {int x, string s};
S s{};
Default structure initialisation; S s{} is equivalent to S s{{}, {}} which expands out to S s{{0}, {""}}
complex<double> z{0, pi};Use of constructor
vector<double> v{0.0, 1.1, 2.2};Use of list constructor
vector<double> v(10, 8.3);Invoking constructor with () (10 elements, all initialised to 8.3)
vector<double> v{10, 8.3};Invoking constructor with {} ((2 elements, initialised to 10.0 and 8.3)
complex<double> z();This is a function declaration! (because in a declaration, an empty () always indicates a function)
complex<double> z{};…whereas this is a default initialiser


NOTE: All the following examples use pointers. However, exactly the same rules and procedures can be applied to references as well

Aliasing is the process of accessing an object via multiple means (not to be confused with type aliasing which is quite different). For example;-

int a = 3; int* b = &a;

Here, the variable a can be accessed directly or via the alias b. There is an implicit assumption that the object's value will be the same regardless of whether it is accessed directly or via the alias, and that if the value is modified (by either means), its new value will become immediately available directly and via any and all alias'

There are a number of rules that dictate which types of expression (the alias) may be used to access a particular type of value. It is important that these rules are strictly followed (hence the often-quoted term strict aliasing rules, although this is not an officially used term) because the compiler will make assumptions based on them and if the rules are broken then undefined behaviour shall result and the compiler is likely to generate incorrect code, especially with more aggressive optimisation levels

The aliasing rules

A pointer b may alias a pointer a if;-


Punning is the technique of forcing an otherwise illegal object alias to be used. In general use, this is (for obvious reasons) not a good thing to do as it deliberately circumvents the type system and can easily lead to undefined behaviour

However, there are cases where it is necessary; low-level driver interfaces to hardware, some low-level network operations, etc

The only safe method to employ in forcing such an operation is to use memcpy() to copy the raw bit pattern from one variable (of a particular type) to another (of another type). In all cases, the following conditions must be met, otherwise undefined behaviour will result;-

Note that in any event, the resulting value (the one copied-to) may or may not hold a value representation that makes sense


A pointer type is specified by the syntax T* varname (a pointer to type T). A pointer holds an address. The address of an object is derived with the & (address-of) operator. A pointer is dereferenced (that is, access is gained to the object being pointed-to) by using the * (dereference) operator

A pointer may refer to any object that has identity; that is, an object that resides at a specific address

char c = 'a';
char* p = &c;p is a pointer and holds the address of c. '&' is the address-of operator
char* q = p;q is a pointer and holds the address of c
char d = *p;d == 'a'. '*' is the dereference operator
int c[] = {7, 11, 13};
int* p = c;p is a pointer to c[0]. The array decays to a pointer
int d = *p;d == 7
int e = p[2];e == 13. See also Arrays
int* a;Pointer to an int
const int* a;Pointer to a const int. The object (int) referred to may not be modified via this pointer
int* const a = b;const pointer to an int (must be initialised)
const int* const a = b;const pointer to a const int (must be initialised)
const volatile int* a;Pointer to a const volatile int
volatile int* const a;const pointer to a volatile int (must be initialised)
char** c;Pointer to pointer to a char
int* a[8];An array of 8 pointers to ints. This will decay to a pointer to pointer; ie, int** a
int (*fn)(char* param);Pointer to a function that takes a char* argument and returns an int. See also Function Pointers
int* fn(char* param);Function declaration that takes a char* argument and returns a pointer to an int
void* a;Pointer to an object of unknown type
nullptr A literal that represents a null pointer 

Pointers to pointers

A pointer may point to another pointer. Such a type is specified by the syntax T** varname (a pointer to a pointer to type T)

char c = 'a';
char* p = &c;p is a pointer and holds the address of c. '&' is the address-of operator
char** pp = &p;pp is a pointer to a pointer and holds the address of the pointer p
char* q = *pp;q is a pointer and holds the address of c. The second '*' is the dereference operator
char** qq = pp;qq is a pointer to a pointer and holds the address of the pointer p
int** a;Pointer to a pointer to an int
const int** a;Pointer to a pointer to an const int
int** const a = b;const pointer to a pointer to an int (must be initialised)
const int** const a = b;const pointer to a (non-const) pointer to a const int (must be initialised)
int* const * const a = b;const pointer to a const pointer to a non-const int (must be initialised)
const int* const * a;Non-const pointer to a const pointer to a const

Use of more than 2 levels of indirection is probably an indication of a design fault


nullptr is a literal that represents a null pointer

Prefer nullptr to 0 or NULL


restrict is not a standard C++ keyword, but it is supported as an extension by several implementations. As such, if it is supported, it will actually be named __restrict or __restrict__


type varname[size];An array with 'size' number of elements is defined like this
varname[n]Array element 'n' is referenced like this
varname[n]Array element 'n' is referenced like this
*varnameDirectly dereference the first element
varnameYields (decays to) a pointer to the first element of the array. For an array int array[4], array would yield a pointer of type int* referring to array[0]. Decay to a pointer causes the size information (number of elements) to be lost
&varnameYields a pointer to the whole array. For an array int array[4], &array would yield a pointer of type int(*)[4]. This will hold the same address as array but is not the same type. Incrementing such a pointer would step the address on by sizeof(int) * 4. Given auto parray = &array, dereferencing the pointer (ie, *parray or parray[0]) will yield the original array, and parray[0][0] or (*parray)[0] would yield array[0]
type(*varname)[3]Specifies an array type of specified length. See example
using typename = type[3]
typedef type(typename)[3]
An array alias. See example
type* v = &varname[n]Create a pointer to element 'n' of an array
type varname[size_x][size_y];A 2 dimensional array (defined as an array of arrays)
type* varname = new type[size];An array allocated on the heap
int nums[3] = {1, 2, 3};A 3 element array populated with 3 values
int nums[42] = {1, 2, 3};A 42 element array. The first 3 elements are populated with values and the remaining elements are initialised to zero
int nums[] = {1, 2, 3};An array initialised with 3 numbers. The array is automatically sized to match the number of initial values (3) and populated accordingly
char name[] = {'H', 'i'};An array initialised with 2 characters. The array is automatically sized to match the number of initial values (2) and populated accordingly
char name[] = {"Hello"};An array initialised with a 'C-style' string literal. The array is automatically sized to that of the string + 1 (for a NULL terminator) and populated accordingly


A reference may refer to any object that has identity; that is, the object resides at a specific address. Unlike a pointer though, a reference is a named alias for the target object rather than a distinct variable. A reference is NOT an object. It is not unreasonable to view a reference as a const pointer to an object that is implicitly dereferenced when required

There are three 'types' of references; those that refer to lvalues (objects that we want to modify), those that refer to const lvalues (objects we don't want to modify), and those that refer to rvalues (generally, temporary references generated by the compiler/run-time environment). See also lvalues And rvalues

int i = 42;
int& r = i;r is a reference to i
int& r2 {i};r2 is a reference to i
int j = r;j == 42 (note implicit dereference; no special syntax)
int* p = &r;p is a pointer to i
r = 83;i == 83
++r;i == 84
const int& s = r;s is a const reference to r (r may not be modified via this reference)
const int& t {42};It is not possible to initialise a reference with an rvalue. Here, a temporary object is created to hold the (rvalue) literal 42, thus allowing the desired effect. The reference must be const. The temporary's scope is the same as that of the reference
int& f(char& param);Function declaration that takes a char reference argument and returns a pointer to an int. The object that param refers to may be modified from within the function
int& f(const char& param);Same as the previous example except that the object that param refers to may NOT be modified from within the function

References and pointers differ in the following ways;-

rvalue References

It is very useful to know if an rvalue reference is pointing to a temporary object (which will not be used after the operation in-hand), because if it is and we want to save that temporary object, we can sometimes perform an inexpensive move operation rather than a (potentially) expensive copy operation

The most common example of this is a function return value where a temporary object being returned will be saved into a caller-specified variable and then the temporary object destroyed. If we can just move the temporary object to the destination variable rather than copying it then this is a "good thing". Another example is an object (such as a std::string or std::list) that is actually just a very small handle to a potentially huge amount of data

int&& r {f()};rvalue reference to a function
int&& r {var};Illegal; rvalue reference to an lvalue (var)
string&& r {"Hello"};rvalue reference to a temporary
void fn(string&& r);A function that takes an rvalue string reference

A classic example of where rvalue references become valuable is in a swap function. A traditional swap function would have to create a temporary and make at least one copy of the parameters offered to it. Using rvalue references, the copy operations are avoided;-

// A "perfect" swap (almost) template<class T> void swap(T& a, T& b) { T tmp {static_cast<T&&>(a)}; // The initialisation may write to a a = static_cast<T&&>(b); // The assignment may write to b b = static_cast<T&&>(tmp); // The assignment may write to tmp }

By using the static_cast<T&&> (resulting in a T&& type), the compiler is able to make use of any optimised operators for the type. In this example, that would be a move-constructor or a move-assignment. The standard library containers support these as well as rvalue versions of insert() and push_back() etc

Because the static_cast<T&&> construct is a little verbose and ugly, the standard library provides the function move(x) which means the same thing. Note that move() does not actually move anything; it is a somewhat misleading name. Regardless, we can improve the example above as follows;-

// Am "improved" swap template<class T> void swap(T& a, T& b) { T tmp {move(a)}; // Move from a a = move(b); // Move from b b = move(tmp); // Move from tmp }

As it stands, the above swap function will only accept two lvalues as parameters. To allow it to accept an rvalue as a parameter as well, we could include the two overloads;-

void swap(T&& a, T& b); void swap(T& a, T&& b);

The standard library containers deal with this issue in a different way, using shrink_to_fit() and clear()

Reference Collapsing

It is possible to take a reference to a reference, though this is only syntactically legal via use of an alias or a template reference parameter

Following on from this point, reference collapsing is the mechanism that allows a function template that takes a forwarding reference to be called with an argument that is already a reference, producing (say) fn(T& && a) which then reduces-down to something that is syntactically legal; fn(T& a)

Reference collapsing occurs in four scenarios;-

Here are the possible combinations and the resulting type that is defined;-

using lref = int&int&
using rref = int&&int&&
using lref_to_lref = lref&int & &int&
using lref_to_rref = rref&int && &int&
using rref_to_rref = rref&&int && &&int&&
using rref_to_lref = lref&&int & &&int&
int && & a = bThis and similar direct (non-alias) syntax is illegal

Forwarding References

The syntax T&& can mean one of two things;-


A structure is a user-defined type. It is defined thus;-

struct structure-name { type1 member1; type2 member2; // ...etc... };

For example;-

struct S { type1 member1; type2 member2; S* member3; // ...etc... };

Fields (Bitfields)

A field element is defined like this;-

type field-name: number-of-bits;

Fields are most commonly used within a struct to allow several very small values to be packed, or to ease mapping onto an externally imposed layout such as a hardware interface. For example;-

struct cache { unsigned int page_num: 12; bool valid: 1; int: 3; // Nameless / unused bool dirty: 1; bool global: 1; };


A union is a user-defined type. Specifically, it is a struct in which all elements are allocated at the same address. For example;-

union union-name { type1 member1; type2 member2; // ...etc... };

An element of a union is called a variant. The union may hold the value of only one variant at a time

Do not use a union type for type conversion; ie, writing to one element and then reading from a different element; it is ugly, error-prone, non-portable, and represents undefined behaviour. See also punning

Encapsulated Unions

It is possible to improve slightly on the raw union by encapsulating it in a class with accessor functions to maintain state and force correct usage by providing 'set' functions, and 'read' functions that check that the correct (ie, the last one that was written) element is being read. Such an arrangement is referred to as a tagged or discriminated union

Encapsulating unions does NOT fix all their problems;-

An alternative to using unions would be to use a set of derived classes. This also has the advantage of not imposing the cost of the union size (the size of the largest element) on the other (smaller) elements

In short, unless all the union elements are simple types (types without user-defined constructors/destructors, copy or move operations), they can cause a lot of trouble and are best avoided. Even if all the elements are 'simple', think twice; there's usually a better alternative


An enumeration is a user-defines set of named integer values (enumerators). There are two types of enum;-

enum animal {dog, cat, polar_bear}; // A "plain" enum enum class vehicle {car, bike, boat}; // An enum class enum struct vehicle {car, bike, boat}; // Exactly equivalent to enum class

Plain Enumerations

A "plain" or "unscoped" enumeration is a user-defined type, akin to a 'C'-type enum

"Plain" enums are termed unscoped because the names defined within them are in the enclosing scope rather than the scope of the enum definition


enum warning {green, yellow, orange, red}; enum traffic_light {red, yellow, green}; // Error: 'red', 'yellow' and 'green' already defined in this scope warning a = 7; // Error: No int warning conversion int b = green; // Ok: green in scope and converts to int int c = warning::green; // Ok: warning int conversion supported warning d = warning::green; // Ok traffic_light e = d; // Error: No warning traffic_light conversion

An anonymous enum may be created if all that is required is a set of constants, rather than an enumeration;-

enum {green, yellow, orange, red};

One use for "plain" enums is in declaring names for tuple elements; their ability to implicitly convert to an integral makes them less awkward to use than a similar "class" enum, though see this example

Class Enumerations

A "class" or "scoped" enumeration is a user-defined type that is scoped and strongly typed. For example;-

enum class warning {green, yellow, orange, red}; enum class traffic_light {red, yellow, green}; warning a = 7; // Error: No int warning conversion int b = green; // Error: green not in scope int c = warning::green; // Error: No warning int conversion warning d = warning::green; // Ok traffic_light e = d; // Error : No warning traffic_light conversion

Sometimes, enumerator values are chosen to provide a bitmask. AND and OR functions can be created to safely manipulate these, such as the following. Note that explicit conversion is necessary because the enum class does not support implicit conversion;-

enum class bitmask {BIT0 = 0x01, BIT1 = 0x02, BIT2 = 0x04, BIT3 = 0x08}; constexpr bitmask operator&(bitmask a, bitmask b) { return static_cast<bitmask>(static_cast<int>(a)) & static_cast<int>(b)); } constexpr bitmask operator|(bitmask a, bitmask b) { return static_cast<bitmask>(static_cast<int>(a)) | static_cast<int>(b)); } void test_flags(const bitmask status) { if (status & (bitmask::BIT1 | bitmask::BIT3) {…} }

Because the '&' and '|' functions are constexpr, they can be used at compile time such as in a switch clause; case bitmask::BIT1 & bitmask::BIT3 {…}. Take care how the & and | functions are used though; they can (and do) return a bitmask value that is not actually legal!

"Class" enums can be used to index a tuple;-

using colours = std::tuple<int hue, int sat, int trans> colours col; // ... enum class colours {hue, saturation, transparency}; // The following are equivalent auto b = std::get<1>(col); auto c = std::get<static_cast<std::size_t>(fields_t::saturation)>(col);

Adding a helper function template reduces the syntax a little. This is general enough to work with any tuple and any enumeration regardless of underlying type;-

// version template<typename T> constexpr typename std::underlying_type<T>::type to_integral(T enumerator) noexcept { return static_cast<typename std::underlying_type<T>::type>(enumerator); } // version template<typename T> constexpr auto to_integral(T enumerator) noexcept { return static_cast<std::underlying_type_t<T>(enumerator); }

Use the helper function;-

auto d = std::get<to_integral(fields_t::saturation)>(col);

Plain Old Data (POD)

Sometimes, especially at a low level or when implementing a container class, it is useful to be able to treat an object as 'plain old data' (POD); ie, just a bunch of structureless bytes. Doing so can massively increase copy performance for example as it avoids the need to call constructors for each of the object's members

A POD type can be safely passed between C++ and 'C' code

For an object to be successfully (ie, without breaking any C++ language guarantees) treated as a POD, it must be a scalar type, a class, struct or union that complies with the following constraints, or an array of such a type;-

A standard library type predicate is_pod<T> is defined in <type_traits> that returns whether a type is a POD or not. This is much more convenient than remembering the above rules and applying them correctly!

A related concept to a 'standard' type is a trivial type which is one that has a trivial default constructor and trivial copy and move operations (among other requirements)

See also Regular Types

Manipulating POD Types


Here is a generalised copy function that uses the standard library type predicate is_pod<T>::value;-

template<typename T> void mycopy(T* to, const T* from, int count) { if (is_pod<T>::value) { // Fast copy memcpy(to, from, count * sizeof(T)); } else { // Slow copy using copy constructor for each element for (int x = 0; x < count; ++x) { to[x] = from[x]; } } }

...or here is a better technique that uses std::enable_if;-

// Fast copy template<typename T, typename = typename std::enable_if<is_pod<T>::value, T>::type> void mycopy(T* to, const T* from, int count) { memcpy(to, from, count * sizeof(T)); } // Slow copy using copy constructor for each element template<typename T, typename = typename std::enable_if<!is_pod<T>::value, T>::type> void mycopy(T* to, const T* from, int count) { for (int x = 0; x < count; ++x) { to[x] = from[x]; } }


Lists can be used for initialising named variables and may be used as expressions in many (but not all) cases. There are two forms;-

T {}Qualified. Means "create object of type T and initialise it with T{}
{}Unqualified. Type must be determined from the context and must be unambiguous

A list is interpreted as follows';-


The standard library std::initializer_list<T> type is used to construct variable length lists. It is mostly used for initialising user-defined containers. For example, the standard library vector has an initializer_list, so this;-

vector<double> v = {1, 2, 3.14};

…is actually interpreted as this;-

const double temp[] = {double{1}, double{2}, 3.14 } ; const initializer_list<double> tmp(temp, sizeof(temp) / sizeof(double)); vector<double> v(tmp);

initialiser_list can be used directly. One useful technique is for passing a varying size list of homogeneous values to a function, for example-

void fn(initializer_list<int> values) { if (!values.size()) { // Empty list passed-in } else { for (auto x : values) {…process…} } } // Call function with a list fn({1, 3, 7, 9, 22, 83});


This is a complete list of statements;-

try {statement-list} handler-list
case constant-expression : statement
default: statement
return expression;
goto label;
label: statement
selection-statementsee below
iteration-statementsee below

if (condition) statement
if (condition) statement else statement
switch (condition) statement

for (for-init-statement; condition; expression) statement
for (for-init-declaration : expression) statement
while (condition) statement
do statement while (expression);

statement statement-list

expression-statement (an expression terminated with a ; (semi-colon), courtesy of the for syntax)

Declaration of a single, uninitialised variable

type-specifier declarator = expression
type-specifier declarator {expression}

handler handler-listsee below

catch (exception-declaration) {statement-list}


A label specifies a point to which program flow may be directed. A label is only used in two contexts;-

A label is defined as label:. However, the format of label is quite different between the two uses; see the appropriate sections for details

A path of execution is considered to include a label only if it contains a jump to that label

Selection Statements

if Statement

An if statement is defined thus;-

if (condition) { statement(s) // Execute this if expression is true }


if (condition) { statement(s) // Execute this if expression is true } else { statement(s) // Execute this if expression is false }

The if clause may (optionally) be specified as;-

if (init; condition)

An example of how the optional init expression may be used;-

if (lock_guard<mutex> lock(uart); is_uart_ready()) { write_uart("Hello"); }

In this example, a mutex is acquired for access to a UART. If the UART is in the appropriate state, then it is written to. lock will go out of scope at the end of the block and so automatically release the mutex

Note that this is nothing that could not be done by other means; it's main advantage is that it restricts the scope of a controlling object (in this example, the mutex)

Constexpr if Statement

A constexpr if statement is defined the same as a standard other if statement, with the addition of the constexpr specifier;-

if constexpr (condition) { statement(s) // Execute this if expression is true }


if constexpr (condition) { statement(s) // Execute this if expression is true } else { statement(s) // Execute this if expression is false }

switch Statement

A switch statement selects from among a set of alternative values (indicated by the case labels). It is defined thus;-

switch (expression) { case value: { // Execute this if expression == value } break; case value2: { // Execute this if expression == value2 } break; …etc… default: { // Execute this if expression != any of the above values } break; }

The switch clause may (optionally) be specified as;-

switch (init; expression)

An example of how the optional init expression may be used;-

switch (auto status = getStatus(); status) { // ... }

Note that, as with the similar feature for if, this is nothing that could not be done by other means; it just allows the scope of a controlling object to be limited

Although not strictly always required by the syntax, if a declaration is made within a switch, always encapsulate the declaration within a block {}. If you don't, the name will pollute the scope of the switch and can lead to subtle errors such as;-

switch (x) { case 1: int a; // Ok - Uninitialised int b = 42; // This will fail (explicit initialisation) my_class c; // This will fail (implicit initialisation) // Fall through... case 2: a += 1; // Error: a is in scope but is not initialised b = 83; // Ok, but original initialisation will fail anyway c.fn(); // Goodness knows! }

It is not legal/possible to by-pass an initialisation. The compiler should detect that b and c have such initialisations and that if the switch expression x is 2 then that initialisation shall be by-passed. This is why the declaration of b and c will fail

It so happens that an int does not require initialisation and for this reason, the declaration of a will succeed. However, the compiler should catch the fact that if x is 2 then a shall be used without being initialised

There may be other subtle combinations of similar errors that may or may not be caught by the compiler. In contrast, if the declarations were within a block, then this would be the result, which is much more sensible;-

switch (x) { case 1: { int a; // Ok - Uninitialised int b = 42; // Ok - Explicit initialisation my_class c; // Ok - Implicit initialisation } // Fall through... case 2: { a += 1; // Error: a not defined b = 83; // Error: b not defined c.fn(); // Error: c not defined } }

In short, always encapsulate switch declarations within a block, therefore limiting their scope to a single case label

Always add a comment ( and/or use a [[fallthrough]] attribute) in-place of any absent break statements to show the intentional omission; eg, // Fall through...

One instance when default should NOT be used is when the switch expression is an enumeration type and the intention is to provide a case label for each enumerator. In this case, including the default label would prevent the compiler from detecting if any of the enumerators had not been accounted for. If there is a need to test for an illegal enumerator value then this is possibly best done separately (prior to the switch) rather than using a default label

Iteration Statements

for Statement

A for statement is defined thus;-

for (for-init-statement; condition; expression) { // Loop body. Executed repeatedly until 'condition' yields false or until explicitly terminated statement(s); }

An alternative to a while statement is a for statement in the following form which combines the condition of the for with its expression. Like the while statement, this still allowing a non-determinate number of iterations but it also has the advantage of not requiring a separate loop control variable; the element being operated on is used for this purpose, and that variable's scope is confined to the loop itself;-

for (int x; x = get_next_x();) { // Process x }

Range-for Statement

A range-for statement is defined thus;-

for (for-init-declaration : expression) { statement(s) // Loop body. Executed for each element that expression yields or until explicitly terminated }

The range-for statement may (optionally) be specified as;-

for (init; for-init-declaration : expression)

In order to make a container type usable with the range-for statement; the following must be defined;-

while Statement

A while statement is defined thus;-

[[arguments]] while (condition) { statement(s) // Loop body. Executed repeatedly until 'condition' yields false or explicitly terminated }

do Statement

A do statement is defined thus;-

do { statement(s) // Loop body. Executed repeatedly until 'condition' yields false or until explicitly terminated } while (condition)

Loop Iteration Control

The iteration of any loop statement, for, range-for, while or do, may be modified from within the body of the loop in several ways

The continue statement is used exclusively to control loop iteration. The break statement is used likewise and also within switch statements. They are defined like this;-

break; // Terminate loop (or switch) continue; // Skip loop iteration

Control Flow Statements

goto Statement

A goto statement is defined thus;-

goto label; // ...The 'goto' statement shall skip over this code... label: // The 'goto' statement shall jump to here

Don't use goto. It's hideous, it subverts the logical flow of the program, it's the source of countless bugs, it's NEVER necessary, EVER!

return Statement

A return statement is defined thus;-

return expression;

Observe the principle of "one point of entry, one point of exit"; a function has one point of entry; the top. It should also have one point of exit; the bottom. Using multiple return statements within a function is ugly, it subverts the logical flow of the program, and can be a cause of error


Many things are expressions; assignment, function calls, object construction, and many others

When parsing an expression, the compiler first extracts lexical tokens from the expression string. It does this following a 'greedy' technique; that is, each token is extracted to make it as long as possible while still being syntactically legal. Tokens are composed of the following elements;-

Token ClassExamplesRef.
Identifiervector, banana, count
Keywordint, for, class
Character literal'a', '\n', U'\U1234abcd'
Integral literal1, 42, 0x83
Floating-point literal1.2, 1.2e-3, 1.2L
String literal"Hello", R"("World")"
Operator+, +=, >>
Punctuation;, {, }, (, ), [, ]
Preprocessor operation#, ##

Expression Evaluation Order

Order of evaluation of sub-expressions within an expression is undefined. Given;-

result = expr1 @A expr2 @B expr3

…where @A and @B are arbitrary operators, it is undefined in which order the expr expressions are evaluated. Operator precedence rules will effect the order in which the expressions are combined, however

Example 1 The effect of indeterminate evaluation order can be significant;-

int fn(int a) { static int b = 3; return a * b++; } int c = fn(1) + fn(2);

Because the value of each sub-expression relies on side-effects from the other, the value of c is indeterminate; actually this invokes undefined behaviour. The result could be (1 * 3) + (2 * 4) or (2 * 3) + (1 * 4)

Example 2

int a = 1; int b = --a + ++a;

…invokes undefined behaviour because at least one of the sub-expressions (in this case, both of them), modifies a value used by the other

Example 3

std::cout << fn() << fn() << fn();

Result is unspecified

Evaluation follows operator precedence rules resulting in expected left-ro-right evaluation

See also Function Argument Evaluation Order

Conditional (tertiary) Expressions

A conditional expression is a more direct alternative to an if statement;-

condition ? expression : expression

For example;-

int a = (b > c) ? b : c; // If b > c then b shall be assigned to a, otherwise c shall be assigned to a

Temporary Objects

When evaluating an expression, it may be necessary to create a temporary object. For example, with the expression (a + b) * c, the value (a + b) must be held somewhere before evaluating the rest of the expression

Unless it is to be bound to a const (lvalue) reference or non-const rvalue reference, and used to initialise a named object, any temporary object that comes into being shall be destroyed at the end of the whole expression (NOT just the sub-expression) in which is was created. For fundamental types, this process is largely irrelevant to the application. However, for more complex types, the lifetime of any temporary object may become an issue

const char* c = (s1 + s2).c_str();
// ...use c...
Error. s1 and s2 are of type string. The string class includes the method c_str() that returns a ref. to the raw plain 'C' string used internally by it. The problem here is that (s1 + s2) creates a temporary object, and so c points to the plain 'C' string of that temporary …which will be destroyed at the end of the expression!
if (strlen(c = (s1 + s2).c_str()) < 42)
  // ...Use c...
Error. The if statement will actually work as intended because the comparison is part of the same expression that creates the temporary and therefore the temporary will still exist. However, subsequent use of c is undefined because, like the previous example, the temporary will be destroyed at the end of the expression
const string& c = s1 + s2;
// ...Use c...
Ok. By assigning the temporary to a (const) reference, its scope is extended
string& c = s1 + s2;
// ...Use c...
Error. The reference is not const. The scope of the temporary object is not extended resulting in a dangling reference
const string c = s1 + s2;
// ...Use c...
Ok. Use the temporary to initialise a new object
fn(s1 + s2);Ok. The temporary will exist until the function call ends

Structured binding follows the same temporary object scope extension rules as shown above

Do not assume that all expressions involving temporary objects yield an rvalue, or assume some other similar relationship; the two concepts are entirely separate

Polymorphism Without a Virtual Destructor

The following example highlights a useful feature;-

class Y { }; class X : public Y { }; X get_x() { return X{}; } void fn() { const Y& a = get_x(); }

Here, the call to the function get_x() yields a temporary object of type X. However, the reference a is of type const Y& (ie, the base of X). The point to note is that despite the base reference, when a goes out of scope and is destroyed at the end of the function fn(), the object that a refers to will still be destroyed correctly; that is, the X destructor shall be called first followed by the (base) Y destructor. This works correctly even without a virtual destructor being defined for Y

This technique only works for local const references; not for class member references or non-local references

Constant Expressions

A constant expression is defined thus;-

// Standard (object) form constexpr type name = expression; // Functional form constexpr name(arguments) { return expression; }

Use constexpr whenever possible; it can provide safer (compile-time) initialisation, and may allow optimisations and use-cases not possible by other means

Constant Evaluation

A consteval expression is defined in exactly the same way as the functional form of constant expression;-

consteval name(arguments) { return expression; }

Like a constexpr, a consteval yields a constant expression

The difference between the functional form of a constexpr and a consteval expression is that the former will fall-back to run-time evaluation if supplied with non-constant expression arguments, whereas the latter will fail to compile. This guarantees that the consteval expression always generates an immediate function; that is, it is evaluated at compile-time and yields a constant expression

In all other regards, the description of the functional form of constexpr applies

Implicit Type Conversion

Integral and floating-point types may be freely mixed in assignments and expressions, and implicit conversion between types is performed in such a way as to try and preserve information. Implicit type conversions that preserve value are called promotions

Sometimes, it is not possible to preserve value. In this case, a narrowing conversion is performed. For example;-

float a = 123456.789; char b = a; // Ok. This is legal but clearly will lose information char c {a}; // Error. The {} initialiser syntax does not allow narrowing conversion

Basic conversion procedure and how narrowing is handled;-

Try to avoid narrowing conversions, but if they are unavoidable (or possible, in (say) a template function), consider the use of run-time checked conversion functions such as narrow_cast<>. This tests for loss of data by comparing the result after the conversion with the original value, at the obvious cost of additional overhead

Type Promotion

Implicit type conversions that preserves value are called promotions. Integral types are promoted before any arithmetic operation is performed. The main purpose is to convert numeric values to the 'natural' size of the underlying machine architecture (ie, int)

For an integral type, as long as the type is smaller than an int, promotion shall be performed. In contrast floating-point types are only converted if necessary (ie, if the types in an expression differ) by following the usual arithmetic conversion rules

Integral Promotion

For example, given the following;-

unsigned char a; unsigned char b; auto c = @ a; auto d = a @ b;

…the following types shall result for the respective arithmetic and bitwise logical operations;-

The implicit promotion of types smaller than int when performing arithmetic or bitwise operations can result in unexpected values being generated, especially when unsigned/signed conversion is implied or when negative signed values are being used. The following examples assume int is 32 bits

Example 1;-

uint8_t a = 0x80U; uint8_t b = ~a >> 4; // Error: 'b == 0xf7' and NOT '0x07'

The reason for the above result is that a is first promoted to an int, resulting in a value of 0x00000080. The ~ operation is then applied, resulting in the value 0xffffff7f. This is then right-shifted by 4, resulting in 0xfffffff7, and then implicitly cast back to a uint8_t resulting in a value of 0xf7

One way to correct this problem is to modify the expression as follows;-

uint8_t b = static_cast<uint8_t>(~a) >> 4;

Example 2;-

int8_t a = -1; uint16_t b = 0x8000U; uint32_t c = 0x80000000U; bool d = a < b; // Ok: 'd == true' bool e = a < c; // Error: 'e == false'

The result d is correct. It's worth understanding why. First, a is promoted to an int maintaining its value of -1. b is also promoted to an int, resulting in a value of 0x00008000 (32768). -1 is less than 32768, hence the result true

The result e is not correct. Here, a is promoted to an int maintaining its value of -1. c is already of a type whose size is ≥ int and so is not promoted. retaining its value of 0x80000000. As a result, a is now converted to the same type as c, resulting in the unsigned value 0xffffffff. This is not smaller than 0x8000000, hence the result false

Usual Arithmetic Conversion

The result of an arithmetic expression between two operands is determined by the "usual arithmetic conversion" rules; the general aim being to produce a result that is as large as the largest operand type. The rules are;-

Explicit Type Conversion (Casting)

Conversion from one type to another may be performed explicitly, using several syntax forms and operators (in vague order of 'niceness' and safety);-

{}Construction. This only allows safe conversions

This takes the form type const_cast<type>(expr)

Cast-away const and volatile qualifiers. This is only safe if the original object was defined as non-const and/or non-volatile (and has since acquired these attributes)

The type being cast from must be a pointer, reference, or pointer-to-data-member

The type being cast to must be the same as that being cast from (except for any const or volatile qualifiers). For example;-

const int* a{}; int* b = const_cast<int*>(a);

const_cast imposes no run-time overhead


This takes the form type static_cast<type>(expr)

Converts between related types such as one pointer type to another in the same class hierarchy, an integral type to an enumeration, or a floating-point type to an integral type. It also does conversions defined by constructors (§16.2.6, §18.3.3, §iso.5.2.9) and conversion operators (§18.4). For example-

void* my_allocator(size_t sz); int* p = static_cast<int*>(my_allocator(100));

For static_cast to work, there must be an implicit conversion available to convert from expr to type (in which case static_cast isn't actually needed), or from type to the type of the expr

static_cast may be used to add const or volatile to a type, but not remove them, eg, assuming X a{};, const X* b = static_cast<const X*>(&a);

static_cast cannot be used on a polymorphic class hierarchy; a dynamic_cast is needed in this case

The address of the destination type may differ from that of the source type. For example, if casting to one of the bases of a multiply-inherited derived type, then clearly not all the bases can reside at the same address (unless they are empty)

As a result, static_cast may impose a (typically very small) run-time overhead for some conversions


This takes the form type reinterpret_cast<type>(expr)

Changes the meaning of a bit pattern (does not change the actual data). Handles conversion between unrelated types such as a pointer to an integer. For example;-

char x[] = "1234"; int* y = static_cast<int*>(x); // Error: No implicit char* to int* conversion int* y = reinterpret_cast<int*>(x); // Ok: Hope you know what you're doing!

An example of an "acceptable" use of reinterpret_cast is in mapping a literal representing (say) a hardware register to a usable type;-

uint32_t* reg = reinterpret_cast<uint32_t*>(0x10004120);

The following conversions are possible;-

  • Conversion of a type to itself
  • Conversion of a pointer to an integral of sufficient size to represent the full range of pointer values (such as uintptr_t). Note that conversion of a pointer to such an integral and back again to the pointer type is guaranteed to yield the same pointer value
  • Conversion of an integral or enumeration to a pointer. Note that conversion of an integral or enumeration to a pointer type and back again is not guaranteed to yield the same integral/enumerated value. An integer value of zero cast to a pointer is not guaranteed to yield the value nullptr (static_cast or implicit conversion should be used in such cases)
  • A value of type nullptr_t can be cast to an integral type. Conversion to nullptr_t requires a static_cast
  • A value of type nullptr_t cannot be cast to a pointer type; this requires a static_cast or implicit cast
  • Conversion of a pointer type to a different pointer type. As long as the alignment requirement of the yielded type is not greater than that of original, the yielded value can be cast back to the original type and yield the same value as the original. Any conversion may result in undefined behaviour if it contravenes the type aliasing rules
  • Conversion of a 'pointer to a member object' to a pointer of a different member object type. As long as the alignment requirement of the yielded type is not greater than that of original, the yielded value can be cast back to the original type and yield the same value as the original
  • A function pointer may be converted to a function pointer of a different type. Calling such a converted pointer is undefined behaviour though. Converting back to the original function pointer type shall yield the original value
  • An rvalue member function pointer may be converted to a member function pointer of a (possibly) different type. Calling such a converted pointer is undefined behaviour though. Converting back to the original member function pointer type shall yield the original value
  • Some implementations and platforms allow a function pointer to be converted to/from void* or to/from any other pointer type

reinterpret_cast is a dangerous (though sometimes necessary) operation. It provides no safeguards. Apart from the obvious issue of possibly casting to a type that yields nonsense results, some other issues that can arise are;-

  • If the size of type is larger than expr then (at least some part of) the resulting cast object shall (at best) contain uninitialised or indeterminate data or (at worse) extend to cover an area of memory that, when accessed, causes a catastrophic failure. Either way, the result is undefined behaviour
  • If the alignment requirements of type are stricter than those of expr (or the cast is that of a pointer to such types) then undefined behaviour will result. On some architectures, merely performing such a cast (and not actually accessing the resulting cast data) can lead to erroneous behaviour

Generally, it is best to consider the only guaranteed safe use of reinterpret_cast to be that of casting a pointer or reference back to its original type in the event that its type has become modified for some reason

reinterpret_cast imposes no run-time overhead, except (possibly) when casting between integrals and pointers, or (on some unconventional platforms that use different pointer representations, depending on type) conversion between pointer types

dynamic_castDynamically checked (at run-time) conversion of pointers and references within a class hierarchy. See dynamic_cast
(type)value'C'-style cast. This uses a combination of const_cast, static_cast and reinterpret_cast to perform whatever cast is specified. As a result it is very dangerous; virtually any cast can be performed and with virtually no safeguards
type(value)Function-style cast . Note that for a built-in or "plain" enumeration type T, T(e) is interpreted in the same way as (T)e (ie, a 'C'-style cast), with all the dangers that brings with it

Think twice before using any cast. Casts are almost always avoidable, and if they are not, confine them to small, well-defined areas; consider providing a function specially for the purpose to isolates the operation and avoid the need to scatter cast operations throughout the application code

Don't cast-away const. Especially don't define a const member function that actually modifies member data by casting-away const from this or from such members. There is always a better way. See also mutable

One possible exception to this rule is if passing const data by reference/pointer to a legacy function that you have no control over and that is known not to modify the argument but which fails to specify it as const. There are many examples of such functions in the standard 'C' library

Casts, whether explicit or implicit, are usually performed at run-time and often (but not always) involve a call to a non-default constructor for the type being cast-to. That constructor will create a new object. This can lead to all sorts of problems;-

class Y { public: fn(); }; class X : public Y { /* ... */ }; X a{}; static_cast<Y>(a).fn(); // Error: This line will compile and run, but it's wrong

The static_cast will invoke the copy constructor for Y and return a newly constructed object. Therefore, the call to fn() will be invoked on a copy of 'a' and not on 'a' itself. When fn() returns, the Y object shall be destroyed. Why you would want to do this at all is another issue entirely!

Templates provide a means of avoiding casts altogether

reinterpret_cast is often non-portable, usually because of differences in type sizes

'C'-style casts and function-style casts are both rather dangerous and best avoided. There are no scenarios where they MUST be used

Here is an alternative explicit conversion function that handles possible narrowing (loss of data) of scalar types;-

template<class Target, class Source> Target narrow_cast(Source v) { auto r = static_cast<Target>(v); // convert the value to the target type if (static_cast<Source>(r) != v) throw runtime_error("narrow_cast<>() failed"); return r; } // Example use auto c1 = narrow_cast<char>(42); // Ok auto c2 = narrow_cast<char>(342); // Will throw an exception if char ≤ 8 bits

Note that this is more likely to throw with floating-point types because of rounding errors. In that case, a range test rather than a hard != is probably a better test. This can be achieved with operator overloading or traits. The standard library round() function is also available


The dynamic_cast operator takes the form type dynamic_cast<type>(expr) and provides a dynamically checked (at run-time) conversion of pointers and references within a class hierarchy. It is useful when it is not possible to determine the correct cast at compile-time. For example, this;-

class Z {} class Y {} class X : Y, Z {}

…gives the following hierarchy;-


Given a pointer pz that refers to the base Z, we can derive a pointer to the base Y;-

X a; Z* pz = &a; Y* py = dynamic_cast<Y*>(pz); // ...or even... auto py = dynamic_cast<Y*>(pz);

The above example shows a very simple hierarchy but it may be arbitrarily complex

Converting Pointers

Converting References


Avoid getting into a situation where you need to downcast or crosscast a non-polymorphic type. There is no guaranteed type-safe way of doing this


This is a complete list of operators, in decreasing order of precedence and with each block containing operators of the same precedence. The following definitions are used;-

OperationSyntaxOverload Impl. Type Ref.
Parenthesized expression( expr )-
Lambda[ capture-list ] lambda-declarator
  { statement-list }
Scope resolutionclass-name :: member-
Scope resolutionnamespace-name :: member-
Global:: name-
Member selectionobject . member-
Member selectionpointer -> memberY* X::operator->()
const Y* X::operator->() const
Subscriptingpointer [ expr ]Y& X::operator[](index)
const Y& X::operator[](index) const
Function callexpr ( expr-list )type X::operator()(expr-list)
Value constructiontype { expr-list }-
Function-style type conversiontype ( expr-list )X::operatortype() const
Post incrementlvalue++X X::operator++(int)
X operator++(X& lhs, int)
Post decrementlvalue--X X::operator--(int)
X operator--(X& lhs, int)
Type identificationtypeid( type )-
Run-time type identificationtypeid( expr )-
Run-time checked conversiondynamic_cast < type > ( expr )-
Compile-time checked conversionstatic_cast < type > ( expr )-
Unchecked conversionreinterpret_cast < type > ( expr )-
const conversionconst_cast < type > ( expr )-
Size of objectsizeof expr-
Size of typesizeof ( type )-
Size of parameter packsizeof...( name )-
Alignment of typealignof ( type )-
Pre increment++lvaluePrefix Unary
Pre decrement--lvaluePrefix Unary
Bitwise complement~expr
compl expr
Postfix Unary
Logical Not!expr
not expr
Postfix Unary
Unary minus- exprPostfix Unary
Unary plus+ exprPostfix Unary
Address of&lvalueY* X::operator&()
const Y* X::operator&() const
Y* operator&(X& lhs)
const Y* operator&(const X& lhs)
Dereference*exprY& X::operator*()
const Y& X::operator*() const
Y& operator*(X& lhs)
const Y& operator*(const X& lhs)
Create (allocate)new typestatic void* X::operator new(size_t sz)
void* operator new(size_t sz)
static void* X::operator new[](size_t sz)
void* operator new[](size_t sz)
Create (allocate and initialise)new type ( expr-list )
new type { expr-list }
Create (place)new ( expr-list ) typestatic void* X::operator new(size_t sz, expr-list)
void* operator new(size_t sz, expr-list)
Create (place and initialise)new ( expr-list ) type ( expr-list )
new ( expr-list ) type { expr-list }
Destroy (de-allocate)delete pointervoid X::operator delete(void* p)
void operator delete(void* p)
Destroy arraydelete[] pointervoid X::operator delete[](void* p, size_t sz)
void operator delete[](void* p, size_t sz)
Can expression throw?noexcept ( expr )-
Cast (type conversion)( type ) expr-
Member selectionobject .* pointer-to-member-
Member selectionpointer ->* pointer-to-memberY* X::operator->*(Y m)
const Y* X::operator->*(Y m) const
Y* operator->*(X& lhs, Y m)
const Y* operator->*(const X& lhs, Y m)
Multiplicationexpr * exprBinary (Arithmetic)
Divisionexpr / exprBinary (Arithmetic)
Moduloexpr % exprBinary (Arithmetic)
Additionexpr + exprBinary (Arithmetic)
Subtractionexpr - exprBinary (Arithmetic)
Shift left 'expr' number of bitsexpr << exprBinary (Bitwise)
Shift right 'expr' number of bitsexpr >> exprBinary (Bitwise)
Less thanexpr < exprBinary (Logical)
Less than or equalexpr <= exprBinary (Logical)
Greater thanexpr > exprBinary (Logical)
Greater than or equalexpr >= exprBinary (Logical)
Equalexpr == exprBinary (Logical)
Not equalexpr != expr
expr not_eq expr
Binary (Logical)
Bitwise ANDexpr & expr
expr bitand expr
Binary (Bitwise)
Bitwise XORexpr ^ expr
expr xor expr
Binary (Bitwise)
Bitwise ORexpr | expr
expr bitor expr
Binary (Bitwise)
Logical ANDexpr && expr
expr and expr
Binary (Logical)
Logical ORexpr || expr
expr or expr
Binary (Logical)
Conditional expressionexpr ? expr : expr-
List{ expr-list }-
Throw exceptionthrow expr-
Assignlvalue = exprBinary Assignment
Multiply and assignlvalue *= exprBinary Assignment
Divide and assignlvalue /= exprBinary Assignment
Modulo and assignlvalue %= exprBinary Assignment
Add and assignlvalue += exprBinary Assignment
Subtract and assignlvalue -= exprBinary Assignment
Shift left and assignlvalue <<= exprBinary Assignment
Shift right and assignlvalue >>= exprBinary Assignment
Bitwise AND and assignlvalue &= expr
lvalue and_eq expr
Binary Assignment
Bitwise OR and assignlvalue |= expr
lvalue or_eq expr
Binary Assignment
Bitwise XOR and assignlvalue ^= expr
lvalue xor_eq expr
Binary Assignment
Sequencingexpr, exprZ X::operator,(Y& rhs)
Z operator,(X& lhs, Y& rhs)

In addition to the above, a user-defined type may also define literal operators

The Overload Impl. Type column indicates the prototype/signature of the operator function when implemented for a user-defined type. For further details, see Overloading Operators

Some precedence examples;-

a + b * cMeans a + (b * c) because * has a higher precedence than +
a = b = cMeans a = (b = c)
a + b + cMeans (a + b) +c
if (x & mask == 0) {…}Means x & (mask == 0). Take care!
if (0 <= x <= 42) {…}Means (0 <= x) <= 42. This is interpreted as follows; 0 <= x yields a bool of true or false. This is implicitly converted to an int yielding 0 or 1. This is then compared with 42 which will always yield true
a+++ 1Means (a++) + 1

Explicit use of bitwise operators such as &, |, ^ etc can sometimes be avoided by using a bitfield

The bitwise operators &, |, ^, etc can be used for logical 'set' manipulation. However, consider the higher-level standard library types set and bitset instead

Overloading Operators

Operator overloading allows conventional notation to be used to manipulate an object of a user-defined type. For example, given two objects of a user-defined class; a and b, it may be useful to check for equality a == b, or to be able to add a + b (whatever 'add' means in the context of the type)

The Overload Impl. Type column in the table of operators indicates the prototype/signature of the operator function when implemented for a user-defined type. Most operators follow the same few function signature/prototype patterns and these are indicated below. For any operators that deviate from the standard patterns, their specific function signature/prototype is indicated specifically in the table and are described in more detail in the following sections

The meaning of Overload Impl. Type is as follows

Note: The arguments and return types are flexible for most operators; in all cases, lhs and rhs can be any type, and is commonly another X type. In the case of a binary operator, rtn-type is often another X but may be a different type. For example, a != operator would probably return a bool;-

Overload Impl. TypeDescription
-Operator may not be overloaded in user-defined type
Prefix Unary

Prefix unary operator. This may be defined as either a non-static member function taking one argument or as a non-member function taking two argument. The operator acts directly on and modifies the supplied object. An operator @ is defined with any of the following;-

X& X::operator@() X& operator@(X& lhs)

Generally, a prefix unary operator member function should return a reference to *this, and a non-member function should return a reference to lhs

Postfix Unary

Postfix unary operator. This may be defined as either a non-static member function taking one argument or as a non-member function taking two arguments. The operator does not modify the supplied object but (typically) creates a copy of it and operates on (and returns) the copy. An operator @ is defined with any of the following;-

rtn-type X::operator@() const rtn-type operator@(const X& lhs)

Binary operator. This may be defined as either a non-static member function taking one argument or as a non-member function taking two arguments. An operator @ is defined with any of the following;-

rtn-type X::operator@(const rhs) const rtn-type operator@(X lhs, const rhs) rtn-type operator@(const lhs, X rhs)

An Arithmetic Binary operator member function often returns a modified copy of *this, and a non-member function often returns a modified copy of the X argument, though in both cases it may be appropriate to return some other type

A conventional Bitwise Binary operation on an integral type returns a similar integral type. For a user-defined type, the return type often follows that of an Arithmetic Binary operator

A Logical (Comparison) Binary operation is usually best defined as non-member function(s) so that the argument types may be specified either way. The operation usually returns a bool value

Binary Assignment

Binary Assignment operator. This may be defined as either a non-static member function taking one argument or as a non-member function taking two arguments. An operator @ is defined with any of the following;-

X& X::operator@(const rhs) X& operator@(X& lhs, const rhs)

Generally, unless there is a good reason not to, a binary assignment operator member function should return a reference to *this, and a non-member function should return a reference to lhs. This allows chaining; a = b = c. An assignment operator that behaves differently will not operate in a 'normal' way and may cause problems within expressions

Following is an example of one of each of the four main operator formats; operator '++' (prefix Unary), operator '!' (postfix Unary), operator '+' (Binary) and operator '=' (Binary Assignment);-

class X { X& operator++(); // Prefix unary Y operator!() const; // Postfix unary X operator+(const rhs) const; // Binary X& operator=(const rhs); // Binary Assignment }


X& operator++(X& lhs); // Prefix unary Y operator!(const X& lhs); // Postfix unary X operator+(const X& lhs, const rhs); // Binary X operator+(const lhs, const X& rhs); X& operator=(X& lhs, const rhs); // Error! Binary Assignment must be a member function

Wherever possible, maintain the normal meaning of operators. For example, + should not (say) perform a square-root operation or something completely unrelated

One obvious exception to this is the way the standard I/O library overloads the operators << and >> to do something completely different. Even so, these still 'sort-of' keep the traditional meaning in an abstract way

Also, maintain the relationships between operators. For example, a = a + 1 conventionally means the same as a += 1 or a = a - -1 or a -= -1, but these are all different operators; = (assignment), + (addition), - (subtraction), += (add and assign), -= (subtract and assign) and -expr (unary minus)

It is usually a good idea to define operators so they are commutative; ie, a + b should give the same result as b + a even if a and b are different types

Operators should be carefully considered and defined as a whole to avoid any discrepancies between them

Special Operators

There are a number of operators that are considered "special" in that they do not follow the normal pattern of arguments and/or return values or their use is not standard. These are;-

All the above are described in the following sections

The following operators are also "special" but are described elsewhere;-

Subscripting []

The subscripting operator [] allows a type (or more specifically, the member(s) of a type) to be indexed like an array. The argument is the index and may be of any type, thus allowing associative arrays to be constructed. For example;-

class X { int m[10]; public: const int& operator[](int index) const { return m[index]; } int& operator[](int index) { return m[index]; } }; // Use the '[]' operators X a; int b = a[3]; a[4] = 42;

Function Call ()

The function call operator () (also known as the Application Operator) allows a type to be directly used as a function name. That is, an object of the type can act like a function; a Function Object. For example;-

class X { int m; public: bool operator()(); // Takes no arguments int operator()(int y); // Takes one argument of type int void operator()(int y, bool z); // Takes two arguments }; // Use the '()' operators X a; bool b = a(); int c = a(42); a(83, true);

Member Selection ->

The dereferencing operator -> supports the important concept of indirection . In practice, this usually means returning a pointer reference to some member object or the internal pointer to some external resource. For example;-

class Y { int m1; int m2; }; class X { Y m; public: Y* operator->() { return &m; } }; // Use the '->' operator X a; int b = a->m1; // Access a member of the return Y object

Note that the variable a is not a pointer, which is a deviation from the standard usage of ->

Increment/Decrement ++/--

The increment/decrement operators are specified with ++ and -- respectively. They are unique in that they may be used in both prefix and postfix operations

class X { int m; public: // Constructor X(int a) : m{a} {} // Pre-increment/decrement X& operator++() { ++m; return *this; } X& operator--() { --m; return *this; } // Post-increment/decrement X operator++(int) { X a{m}; ++m; return a; } X operator--(int) { X a{m}; --m; return a; } }; // Use the '->' operator X a; ++a; --a;

Consider not providing postfix increment/decrement for a type. They are not as efficient as the prefix operators and are less frequently used anyway

Pointers To Members .*/->*

It is, of course, possible to take the address of a class member in the normal way;-

class X: { public: int m; }; X a; b = &a.m; // b is a pointer to member m of object a

However the above example is NOT what a pointer-to-member is

A pointer-to-member is best thought of as a named offset into a class (type) rather than as a normal pointer with a specific absolute address. The operators .* and ->* are used in this context to dereference such a pointer

Despite being thought of as a named offset, a pointer-to-member is most useful when the the user of the pointer does not (cannot) know the exact member being referred to

Here is an example that uses a base class with a number of pure virtual functions defined (possibly the most common structure for such cases);-

class Y { public: int m1{}; const char* m2{}; virtual bool fn1(int a) = 0; virtual bool fn2(int a) = 0; virtual bool fn3(int a) = 0; }; class X : public Y { public: bool fn1(int a) override { /* ... */ } bool fn2(int a) override { /* ... */ } bool fn3(int a) override { /* ... */ } }; // Create an object of type X and also take its address X a; X* ap = &a;

Using the above definitions, this is how we might use pointers-to-data-members;-

// Create alias' for pointer-to-data-member types (an alias makes subsequent use a bit cleaner) using pint = int Y::*; using pcharp = const char* Y::*; // Create two pointer-to-members and initialise them to point to the two base data members pint m_int = &Y::m1; pcharp m_charp = &Y::m2; // Use the pointer-to-members a.*m_int = 81; // a.m1 == 81 ap->*m_charp = "Hello"; // a.m2 == "Hello"

…and this is how we might use pointers-to-function-members;-

// Create alias for pointer-to-function-member type using pfn = bool (Y::*)(int); // Create a pointer-to-member and initialise it to point to one of the member functions pfn fn = &Y::fn2; // Use the pointer-to-member bool r1 = (a.*fn)(42); // This will call X::fn2() for object a bool r2 = (ap->*fn)(42); // This will also call X::fn2() for object a

The above examples are very simple. When they get more complex, it is easy to get the syntax wrong. The standard function std::invoke may be used as an alternative to the above syntax. Expanding on the previous example;-

bool r3 = std::invoke(fn, a, 42); // This will call X::fn2() for object a

Logical AND/OR &&/||

These two binary operators are of the form expr1 && expr2 and expr1 || expr2 respectively

Don't overload the operators && or ||; doing so will result in trouble at some point

Sequencing , (comma)

This operator is of the form expr1 , expr2

Don't overload operator , (comma); doing so will result in trouble at some point

Notwithstanding the previous warning about not overloading , (comma), here is an example of doing exactly that. This method allows the subscripting [] operator to (at least give the impression of) taking multiple indices;-

enum city {london, paris, tokyo, new_york, …}; pair<city, city> operator,(city from, city to) { return make_pair(from, to); } map< pair<city, city>, unsigned int> distance_km; distance_km[london, tokyo] = 9554;

Conversion Operators

A user-defined type may define conversion operators with the notation operator T(). Such operators convert from the user-defined type to the type T

Here is an example of a class X implementing an assignment operator that takes an int and a complimentary conversion operator to convert an object of type X to an int;-

class X { int m{}; public: // Assignment from an int X& operator=(int val) noexcept { m = val; return *this; } // Convert an X into an int operator int() const noexcept { return m; } }; // Use the assignment and the conversion operators X a; a = 42; int b = a; // Implicit conversion; b == 42 cout << "a = " << a << ", b = " << b << endl; // Implicit conversion for a; Outputs 'a = 42, b = 42'

Literal Operators

Literal operators allow new literal notations to be defined for built-in and user-defined types so that a user-defined suffix applied to a "naked" literal value such as 123 or "Hello" or 1.23 can be interpreted specially

A literal operator function is defined with the notation operator"" _U, where U is the "unit" suffix. This does not fit into the normal pattern of operators as there isn't (by default) a "" operator

Unlike most other operators, a literal operator is very restricted in the types of arguments it may take. It must take one of the following forms;-

constexpr rtn-type operator"" _U(unsigned long long n); // Unsigned integer constexpr rtn-type operator"" _U(long double n); // Floating point constexpr rtn-type operator"" _U(const char* p); // 'C' string representation of integer or floating point template<char... chars> // Template literal representation of integer or floating point constexpr rtn-type operator"" _U(); constexpr rtn-type operator"" _U(const char-type* p, size_t sz); // 'C' string literal constexpr rtn-type operator"" _U(char-type c); // Character

For an operator "unit" of blob, here are example uses;-

123_blob // Unsigned integer - calls operator"" _blob(123) // A very long unsigned integer - calls operator"" _blob("1234567890123456789012345678901234567890") 1234567890123456789012345678901234567890_blob 123.45_blob // Floating point - calls operator"" _blob(123.45) "Hello\n"_blob // String - calls operator"" _blob("Hello\n", 6) R"(Hello\n)"_blob // (Raw) string - calls operator"" _blob("Hello\\n", 7) 'A'_blob // Character - calls operator"" _blob('A')

Here is a more complete example;-

class X { double time; public: // Constructor constexpr X(double t = 0) : time{t} {} }; // Literal operators to give an X with a s, ms, or us time scaling constexpr X operator"" _s(unsigned long long t) { return X{static_cast<double>(t)}; } constexpr X operator"" _ms(unsigned long long t) { return X{static_cast<double>(t) / 1000}; } constexpr X operator"" _us(unsigned long long t) { return X{static_cast<double>(t) / 1000000}; } // Use the literal operators X a{123_s}; // a.time == 123.0 X b = 123ms; // b.time == 0.123 X c; c = 123us; // c.time == 0.000123

A template literal operator is one that takes its arguments as a variadic template parameter pack rather than as function arguments. For example;-

// Template literal operator template<char... chars> constexpr int operator"" _blip(); // Using the template literal operator, the following would result in the call // operator"" _blip<'1', '2', '3'>() 123_blip

The implementation of a template literal operator typically needs to step through each character in-turn and construct a value. In order to do this, some helper functions are useful. The following example implementation of operator"" _bcd converts a string of digits into a BCD number;-

// Check that string represents a decimal value template<typename T, typename... U> constexpr unsigned int bcd_is_decimal(const T c, U...) { static_assert(c != '0', "BCD literal is not decimal"); return 0; // Return a dummy int to fulfil constexpr requirements } // Helper function; expect only one digit and extract it template<typename T> constexpr unsigned int bcd_helper(T c) { return static_cast<unsigned int>(c - '0'); } // Helper function; extract one digit and then process remainder template<typename T, typename... U> constexpr unsigned int bcd_helper(T c, U... tail) { return ((static_cast<unsigned int>(c - '0') << (4 * sizeof...(tail))) | bcd_helper(tail...)); } // Operator function template<char... chars> constexpr unsigned int operator"" _bcd() { return (bcd_is_decimal(chars...), bcd_helper(chars...)); } // Use the operator function; this will set 'a' to 74565 binary decimal (12345 bcd) constexpr unsigned int a = 12345_bcd;

Note that this example checks that the first digit of the literal is not zero; if it is then that indicates a non-decimal (ie, binary 0b, octal 0n, or hex 0x) value (which makes no sense in this context)

Memory Management

Free Store (Heap)

The operators new and delete allocate and de-allocate objects on the free store (heap) memory. They also have counterparts, new[] and delete[] for allocating/de-allocating arrays though the existence of the former is not obvious from the syntax. These operators are used as follows;-

T* var = new T{initialiser}; T* var {new T{initialiser}}; delete var; T* array_var = new T[n]{initialiser}; T* array_var {new T[n]{initialiser}}; delete[] array_var;

Using new and delete expressions in their raw form requires care; memory leaks, or incorrect application of delete on an allocated object can cause serious problems

Performing a new but not a subsequent delete will leak memory

Performing a delete and then later using the object at the deleted address, or performing a delete on an object not allocated by new, on an object that has already been deleted or on an invalid address are all undefined behaviour and usually disastrous


The function std::set_new_handler() may be called by an application to register a callback (ie, a new-handler) that shall be called if operator new fails to fulfil an allocation request. It returns the previously registered callback function and is declared as;-

using new_handler = void(*)(); new_handler set_new_handler(new_handler p) noexcept;

If new fails to fulfil a memory request, it shall call the registered new-handler function. If the new-handler returns normally (ie, it does not throw an exception or terminate the program) then new shall try to perform the allocation again. The cycle repeats until the allocation succeeds or the new-handler does not return normally. This behaviour implies that the new-handler must do one or more of the following;-

Example Set the new_handler so that all new operations that fail to allocate will terminate the application rather than throwing an exception or returning nullptr (an exception could still be thrown by a constructor);-


Overloading new And delete

The new expression and operator new

new is actually composed of what may be thought of as two functions; the 'new expression', and operator new. In general use, this distinction is not apparent, but it is useful in order to fully understand the role of set_new_handler and is critical in understanding overriding or overloading operator new;-

The new expression

This is the function that is directly called by an expression such as X* a = new X;

It will first call operator new to acquire some memory of size (eg) sizeof(X)

If the memory acquisition is successful then the appropriate constructor is executed for the allocated type, within the acquired memory. If the construction fails (ie, throws an exception) then operator delete is executed to free the allocated memory and the exception is re-thrown

If the memory acquisition fails then a std::bad_alloc exception is thrown or (if the invoked operator new is a non-throwing version) nullptr is returned to the application

The new expression can also allocate groups of objects, as an array. This occurs with an expression such as X* b = new X[3];

In this case, an appropriate operator new[] is called to acquire some memory of size (eg) sizeof(X) * 3, and if successful, then it executes the constructor for each element of the array

If any constructor fails (ie, throws an exception), then the whole allocation is considered to have failed, the destructor is called for any successfully constructed elements and the appropriate operator delete[] is executed and the exception is re-thrown

It is not possible to override the new expression. It can be thought of as being aware of any overrides/overloads to operator new though and shall invoke the appropriate version

operator new

The sole purpose of this function is to allocate memory of the requested size. Exactly how it does this, and details such as alignment etc, is implementation-specific

operator new may be overridden; globally and/or specifically for a type. If it is, it should follow these conventions;-

  • On success, it should return a non-null void* to the allocated memory
  • On failure, if the new-handler is not nullptr (ref. std::get_new_handler()) then it should be called, followed by a further attempt to fulfil the allocation request. This process repeats forever until it succeeds or until new-handler is nullptr. On failure, if operator new is a throwing version, it should throw a std::bad_alloc exception (the new-handler may also do this), and if it is a non-throwing version, it should return nullptr
  • A valid pointer should be returned even if zero bytes are requested. This is usually achieved by allocating a single byte instead
  • It should be able to handle an unexpected size (ie, a size that does not match that of the object it is designed to allocate - see example)
operator new[]

This can be, and often is, identical to (or simply calls) operator new

It can be different to operator new though, should the application need to handle array allocation differently to single object allocation

The delete expression

As with new, delete is also composed of what may be thought of as two distinct functions;-

The delete expression

This is the function that is directly called by an expression such as delete a;

It will first call the destructor for the object. This is why it is undefined behaviour to pass a void* to the delete expression

It will then call operator delete to free the memory previously allocated by operator new

This is one reason why a destructor should never throw an exception; doing so will almost certainly cause a memory leak

The delete expression can also de-allocate arrays of objects. This is performed by using the form delete[] b

First, the destructor is called for each element of the array, followed by a call to an appropriate version of operator delete[]

It is not possible to override the delete expression. It can be thought of as being aware of any overrides/overloads to operator delete though and shall usually   invoke the appropriate version

operator delete

This function is expected to free the memory previously allocated by operator new

operator delete may be overridden and should be done so to match any override of operator new. If it overridden, it should follow these conventions;-

  • A request to delete nullptr is legal and should always succeed
  • Like operator new, it should be able to handle unexpected size values
operator delete[]

This can be, and often is, identical to (or simply calls) operator delete, though like operator new[], it may do something else

Some reasons why one may wish to replace operator new and operator delete operators include;-

Standard global operator new and delete prototypes

The standard global function prototypes are as follows. For each version of operator new, there is also an operator new[] (though it is not shown here for brevity). Similarly for operator delete and operator delete[];-

Overloading global operator new and delete

'Non-throwing' operator new and delete

Standard type-specific operator new and delete prototypes

It is also possible to overload operator new and operator delete for a specific user-defined class. For each version of operator new, there is also an operator new[]. Similarly for operator delete and operator delete[];-

Overloading type-specific operator new and delete

General operator new and delete overloading considerations

The variety of operator new and operator delete functions can appear confusing

One way of looking at this is that the standard versions (N1, N2, MN1 and D1 and MD1) are invoked by the new and delete expressions unless one of the other versions are defined

There are a couple of additional rules as indicated by the above prototype lists, but this is the general principle

Any version of operator new may be invoked (assuming it is defined) by providing the appropriate arguments to the new expression

However, the delete expression does not take any arguments (other than a pointer to the memory to delete) and so any explicit call to the delete expression will only behave correctly for non-placement allocations. That is, the delete expression will not behave correctly for allocations made with N6, N7, MN3 or MN4

The way around this is that if using one of the other versions of operator delete, it must be called directly, and not via the delete expression. This also means that the object's destructor must be explicitly called before deletion of the memory. For example;-

void* p = ::operator new(sizeof(X)); // Allocate only - no construction X* a = new(p) X{}; // Placement-new - construction a->X(); // Destruction ::operator delete(p); // Free memory

There is also a more complete example

new and delete within a class hierarchy

Placement new And delete

A placement operator new function is one of the forms N5, N6, N7, MN3 or MN4

The following example illustrates the basic use of placement new;-

// Allocate some raw (uninitialised) memory void a* = malloc(sizeof(int)); // Placement new. This constructs an object within the raw memory int* b = new(a) int{42};

Here is a more complete example. It defines an allocator object that performs the actual memory allocation. It then defines a placement operator new function that uses the allocator object;-

// A custom allocator object class allocator { // ...Whatever members are required to implement the allocator... public: // Allocate void* alloc(std::size_t size) { // ...allocate some memory of size 'size' and return a pointer to it... return p; } // Free void free(void* p) { // ...free the memory specified by p... } }; // placement operator new void* operator new(std::size_t size, allocator& alloc) { // Pass the allocation request on to the supplied allocator object return alloc.alloc(size); }

Using the above definitions, we could allocate memory from a specific allocator object as follows;-

extern allocator persistent; extern allocator shared; // Allocate two 'X' objects in different 'allocator' areas X* pp = new(persistent) X{initialiser}; X* ps = new(shared) X{initialiser};

Placement Delete

A placement operator delete function is one of the forms D7, D8 or MD6

Here is an example destroy() function that is suitable for deleting memory allocated with the above allocator;-

// Destroy object allocated with placement new using an allocator object template<typename A, typename T> void destroy(A& alloc, T* ptr) { ptr->~T(); // Explicitly call destructor alloc.free(ptr); // Free memory } // Delete the two object allocated in the previous example destroy(persistent, pp); destroy(shared, ps);


Defined in the standard header <new> as;-

constexpr T* std::launder<typename T>(T* p);


Consider the following:-

struct X { const int m1 }; X a{42};

The member m1 is const and so following the above, the compiler can assume that any subsequent reference to a.m1 will always have the value 42, and may perform some optimisations based on this assumption

The type X is a trivial type, so it is safe not to call its destructor. Therefore, it is legal to use placement new such as;-

X* b = new(&a) X{83};

This will simply create another instance of X, overwriting the original instance. But there is a problem; the compiler can assume that any subsequent reference to b->m1 will always have the value 83, and this would be correct. However, the original assumption that any reference to a.m1 yielding the value 42 is no longer (or at least, should no longer be) true

In reality, it is likely that the compiler will return 42 for a.m1 though. Essentially, the program is now broken; the const has been implicitly cast-away and the member variable modified, leading to undefined behaviour

This problem can be solved by accessing a.m1 as *std::launder(&a.m1). This effectively prevents the compiler from maintaining the assumption that it once held about the value of a.m1, and forces it to re-evaluate it


Another case where std::launder is useful is in circumventing the rule that states that a new object cannot be accessed via a pointer to the old object if the two pointers are of different types. For example;-

aligned_storage<sizeof(int), alignof(int)>::type c; new(&c) int{54}; int* d = std::launder(reinterpret_cast<int*>(&c));

Without the std::launder, the above would not guarantee a correct result

Error Handling

Preconditions And Postconditions

Most functions make some assumptions about the arguments passed to them and the state of any objects that the function may deal with. These preconditions may be implied (assumed) or explicitly tested for within the function. Similarly, the postconditions represent a guarantee that the function makes to its caller about the state of any values returned or any modifications it may make to any other objects

Preconditions and postconditions are important in maintaining invariance

There are several ways of dealing with preconditions;-

In reality, a combination of the above approaches may be suitable in any one situation

The standard C++ implementation supports two mechanisms for the checking of preconditions (and postconditions);-


The std::terminate() function is defined in the standard header <exception> and may be called explicitly from within normal code if other error handling techniques are not an option

By default, std::terminate() will call abort(). This behaviour may be changed by specifying an alternative handler function to std::set_terminate(). For example;-

// From <exception>... using terminate_handler = void(*)(); // Our new terminate() handler [[noreturn]] void my_handler() { // Handle termination } // Establish custom handler terminate_handler old = set_terminate(my_handler); // Do things that may cause terminate() to be called... // Restore original handler set_terminate(old);

The program is exited via an implicit call to std::terminate() if any of the following conditions are met;-

If a program terminates owing to an uncaught exception, it is implementation-specific as to whether destructors are called or not. This may depend on the environment; for example, if invoked from a debugger then it is probably desirable NOT to call destructors


It is not uncommon for a program (or part of a program) to be able to detect an error but have no idea how to deal with it. For example, a library function that has no concept of how it is being used

Exceptions provide a mechanism for such a program (or part of a program) to propagate the error back up the call stack in the general hope/expectation that some code, somewhere will know what to do about it; that is, the exception mechanism provides a means of getting error information from the point of detection to the point of handling

There are some key concepts that make the exception mechanism reliable/usable. These are; the exception safety guarantee (which are central to effective recovery of run-time errors), and Resource Acquisition Is Initialisation (RAII). Both of these concepts rely on the specification of invariants

The construct for preparing to handle the possibility of an exception is the try block

The two basic constructs for propagating an exception and handling it are called throw and catch. Here is an example;-

// Our exception type. In this case, we have no actual data to pass struct serious_error {}; void do_something() { // ... if (something_has_gone_badly_wrong) { throw serious_error{}; } } void fn() { // Do something and catch any exceptions if necessary try { do_something(); // No error - carry on... } catch(serious_error err) { // Error - handle it... } }

In the above example, if do_something() were to throw an exception other than serious_error (or it called some other function that threw some other exception) then the calling function fn() would not handle it. Instead, the exception would propagate further up the call stack to whatever called fn()

Throwing An Exception

Promising Not To Throw

A function may be declared as not throwing any exceptions by using noexcept. For example;-

void fn() noexcept;

This declares a guarantee that the function will not throw or propagate any exceptions. If this guarantee is broken at run-time then the program will end immediately by calling std::terminate() (no destructors further up the call-tree shall be called)

When is noexcept not noexcept?

Consider the following;-

void fn(X a) noexcept; // Invoke fn() X b{}; fn(b);

The function takes an argument of type X by-value. On the face of it, the fact that it is declared noexcept is not unreasonable. However, consider what could happen if the constructor for X could throw an exception. When the function is called, a copy of b is made and it is this copy that is passed to fn(). What would happen if the constructor threw an exception at this point? The exception is not being thrown from fn() and so, technically, the noexcept condition is not violated. The question, though, is whether the noexcept is misleading

In cases like this, it may be more "in-the-spirit" to declare the function as not being noexcept

A general approach to this could be to change the function declaration to;-

void fn(X a) noexcept(is_nothrow_copy_constructable<X>::value);

Catching An Exception

There is a school of thought which says that catching any exceptions is more trouble than it's worth. That is, the program should allow any and all exceptions to make their way up to main() and terminate the program (possibly with some diagnostic output first)

The reason for this thinking is that even if an exception is caught, it is often very difficult to know what to do with it and to recover in any safe and meaningful sense from whatever error is being indicated. Often, the only exception that can be usefully handled is a new bad_alloc error, and even this can be avoided by using the non-throwing version of new and employing more traditional methods for handling the error

There are, of course, other philosophies

Rethrowing An Exception

Exceptions Within Threads

When Not To Use Exceptions

For various historical and practical reasons, there are cases where exceptions cannot be used (or it would be unwise to use them);-

In order to make best use of exceptions within a program, a solid, simple strategy needs to be put in place. Specifically, key functions or subsystem should be designed to either always succeed or fail in a controlled and well-defined way that leaves the state of the program consistent with no lost or broken resources

A function that throws an exception (or fails to catch an exception) should deal with any resource cleanup at the time. It should not rely on its caller to do it for it

When using external libraries, it may be necessary to convert from one error-handling strategy to another. For example, checking for errno after a system function call and throwing an exception if appropriate

Unless a function guarantees noexcept, assume it might throw an exception and protect against this when handling resources

Even (apparently) simple operations such a =, < and sort() may throw exceptions

The exception mechanism is intended to provide a consistent error handling method spanning multiple modules and libraries, possibly developed independently of each other. It also shifts error handling code out of the main flow of execution into specific (catch) blocks which keeps the main flow cleaner and makes the error handling more obvious and visible

The exception mechanism is intended to be used. That is, an error condition does not have to be particularly rare or particularly catastrophic in order to warrant the use of an exception. An error may be considered quite common, and/or not particularly disastrous (as is the case with many I/O operations), but the exception mechanism could still be appropriate. "Exception" should be interpreted as "something that the code was unable to do" rather than "we're all going to die!"

Most large programs will be expected to throw and catch at least some exceptions during a normal and successful run

Notwithstanding this, see also this option

Do not allow exceptions to be emitted from destructors

Only throw objects that are user-defined types specifically defined for the purpose, rather than (say) an int. This will minimise the chance of two exceptions (possibly from different libraries, for example) being confused

Do not use exceptions to perform non-error asynchronous tasks, such as (say) a key entry or I/O interrupt. There are other mechanisms to handle this sort of activity and using exceptions in the role is an abuse of the exception mechanism. It is worth noting that an implementation will typically optimise the exception handling mechanism based on the assumption that it is used only for ("out of band") error reporting

Older code may use the following syntax;-

// Specifies that no exceptions shall be thrown void fn() throw(); // Specifies the types of exception that may be thrown void fn() throw(exc1, exc2);

Both these forms are now deprecated. The first has been replaced with noexcept, and the second proved unsuccessful and has been abandoned

Function try Blocks

It is possible to define an entire function body as a try block. For example;-

void fn() try { // Do some work... } catch (...) { // Catch all exceptions... }

For most functions (such as the above example), there is very little to be gained by this syntax. However, function-try blocks are more useful in constructors

Normally, if an exception occurs within a base or member initialiser then the exception is passed-up to whatever invoked the constructor rather than the constructor itself. A function-try block allows the latter. For example;-

class X { vector<int> v; public: X(int); } X::X(int size) try { // Member construction :v(size); // Rest of construction... } catch (std::exception& err) { // If an exception is thrown by the vector<T> construction then we will // catch it here rather than it being passed up to the caller of X() }

Exception Guarantees

A function that leaves the program in a valid state, with no resource leaks and no inconsistencies is considered exception-safe

Generally, for a class to be exception-safe, it must have an invariant. Objects that are not classes but have some relationship to each other (a relationship that is assumed at all times) must also have an invariant. If such invariants prove false (not maintained) then exception-safety will usually be compromised

Before an exception is thrown, all objects that may be effected must be placed into a valid state (a state that meets each object's invariant). Unfortunately, the state chosen, while valid, may not be the best one for the caller

A function should offer one of the following three exception-safety guarantees;-

The basic guaranteeIf an exception is thrown, the object (or objects) being operated on (and by extension, the whole program) will always be left in a state that meets its invariants. Any co-dependencies it shares with other objects shall remain valid and meet all the invariants for the object (though the state may have changed, possibly in unpredictable ways). No resources shall be leaked
The strong guaranteeIf an exception is thrown, the object (or objects) being operated on (and by extension, the whole program) will be left in exactly the same state it was in before the function started. It is as if the function had never been called, apart from the fact that there is now an exception working its way up the call stack!
The nothrow guaranteeThe function shall never throw an exception and shall always run to completion (and, one assumes, always leaves the object(s) being operated on in a valid state). All operations on any built-in or pointer type offer this guarantee

Exception-Safe Construction

The nothrow guarantee is the most desirable but often impossible to provide if the function is anything other than trivial and/or deals with anything other than built-in types; many 'innocent looking' standard container operations may throw, for example

A general design technique that is often used to provide the strong guarantee is that of copy-and-swap; that is, copy the object (make a temporary), modify the copy (if an exception is thrown at this point then the original remains unchanged), and if all is well, swap the copy with the original using a non-throwing swap()

Offering the strong guarantee rather than the basic guarantee is highly desirable and is often relatively simple as long as the function is dealing with data that it has full visibility of, and control over. If it needs to invoke other functions that may themselves throw then offering such a guarantee becomes much harder even if those functions offer the strong guarantee as well; what happens if the first function call succeeds and the second one fails and throws an exception? Is it possible to undo the effect of the first function?

Another possible obstacle to offering the strong guarantee is that there may be an unacceptable cost involved; the copy-and-swap technique is not free—by definition it involves creating a temporary object

The presence of noexcept is not an indication of a function offering the nothrow guarantee. It is an indication that if an exception is thrown then something has gone seriously wrong and the program is now in an undefined state. The nothrow guarantee is a feature of a function's implementation, not its declaration. See also unexpected() and set_unexpected()

The only time no exception safety guarantee can be offered at all is if the function relies on some other (legacy) function that is itself not exception-safe

The exception safety of a function is only as strong as that of the weakest operation it performs (assuming recovery from the effects of the weakest operation is not possible)

Whenever possible, use pointer manager objects

Although inferior to following true RAII principles, if it is absolutely necessary to use raw resources ("naked" pointers etc), then the following technique can provide exception safety;-

// Define a class that will call an arbitrary function from its destructor template<typename F> struct final_action { F clean; final_action(F f): clean{f} {} ~final_action() { clean(); } }; // Define a function that deduces the type of an action template<class F> final_action<F> finally(F f) { return final_action<F>(f); } // A function that uses our 'finally' mechanism void fn() { int* p1 {}; int* p2 {}; // Protect our "naked" resources auto cleanup = finally([&] { delete p1; delete p2; }); // Create some resources (new may throw an exception) p1 = new int[42]; p2 = new int[83]; // Carry on. When 'cleanup' goes out of scope, it shall tidy-up our resources }

unexpected() and set_unexpected()

The function [[noreturn]] void unexpected() is called in the event that an exception is thrown from a function marked as noexcept


Two different syntax styles are available for declaring a function; the traditional 'C'-style;-

// Using prefix return-type syntax [[attributes]] static extern inline constexpr return-type name(argument-list) noexcept; [[attributes]] static extern inline constexpr auto name(argument-list) noexcept;  [[attributes]] static extern inline constexpr decltype(auto) name(argument-list) noexcept; 

…or this (suffix return-type syntax);-

// Using suffix return-type syntax [[attributes]] static extern inline constexpr auto name(argument-list) -> return-type noexcept; 

A function is defined like this ('C'-style syntax); all three forms follow the same pattern;-

// Using prefix return-type syntax [[attributes]] static extern inline constexpr return-type name(argument-list) noexcept { // body return return-expression; }

…or this (suffix return-type syntax);-

// Using suffix return-type syntax [[attributes]] static extern inline constexpr auto name(argument-list) -> return-type noexcept { // body return return-expression; }

All functions consist of the following components;-

A function may also be declared with a previously defined type. For example;-

// Function type declaration using fn_t = void(*)(int, const double); // Function declaration fn_t fn();

This technique may only be applied to a function's declaration; its definition must be made in the normal way; void fn(int a, const double b) { /* ... */ }. See also Aliases and Function Pointers

In addition to the above, a member function may also be specified as;-

Here is a rather complex example using many of the above options;-

struct S { [[noreturn]] inline auto f(const unsigned long int* const param) const noexcept -> void; };


Return Type

Deduced Return Types

Suffix Return Type
Function's Type Top Level Argument Qualifiers

The examples 1 and 2 above demonstrate an important issue; that is, top level const and/or volatile qualifiers are ignored when determining a function's type

The following shows how to determine the number of, and types of arguments of a function (and a lambda expression), and its return type, given only a pointer to the function;-

#include <tuple> // A traits type used to extract the function attributes template <typename T> struct func_traits : public func_traits<decltype(&T::operator())> {}; // Specialisation for function pointers template <typename RTN, typename... ARGS> struct func_traits<RTN(*)(ARGS...)> { using return_type = RTN; enum { num_args = sizeof...(ARGS) }; template <size_t num_args> struct arg { using type = typename std::tuple_element<num_args, std::tuple<ARGS...>>::type; }; }; // Specialisation for lambdas template <typename LMB, typename RTN, typename... ARGS> struct func_traits<RTN(LMB::*)(ARGS...) const> { using return_type = RTN; enum { num_args = sizeof...(ARGS) }; template <size_t num_args> struct arg { using type = typename std::tuple_element<num_args, std::tuple<ARGS...>>::type; }; }; // A function template that takes an arbitrary function/lambda as an argument template <typename T> void fn(T&& t) { using traits = func_traits<typename std::decay<T>::type>; // Determine the function/lambda attributes using return_t = typename traits::return_type; // Return type auto num_args = traits::num_args; // Number of arguments using arg0_t = typename traits::template arg<0>::type; // First arg type // ... }

The above could be invoked as follows and the function 'fn' would be able to determine the supplied function/lambda arguments etc;-

// A simple function void my_function(int a, float b, int c) {} // A lambda auto my_lambda = [](int a, int b) { return 3.14; }; // Function 'fn' may be called with an arbitrary function/lambda reference fn(my_function); fn(my_lambda);

Function Argument Evaluation Order

Example 1

void fn(int a, int b); int get_int(); fn(get_int(), get_int(), get_int());

In the above example, the order in which the three calls to get_int() shall be executed is unspecified

Example 2

using fp_t = int(*)(int, int); fp_t* get_fn(); int get_int(); int c = get_fn()(get_int(), get_int());

In the above example, the order in which the two calls to get_int() shall be executed is unspecified. However, it is guaranteed that the call to get_fn() shall be evaluated first

Example 3

int a = 1; fn(a++, a);

Because at least one of the two expressions a++ and a modify a value used by the other, the result is undefined behaviour

Result is unspecified

Example 4

class A {}; class B {}; void fn(std::unique_ptr<A>, std::unique_ptr<B>); fn(std::unique_ptr<A>(new A), std::unique_ptr<B>(new B));

The above call to fn() actually contains 4 function calls in its arguments; two calls to new and two calls to std::unique_ptr

The execution order of the four function calls is indeterminate; it could be any of;-

Only the first two sequences are exception-safe. The others will leak memory if the second new in the sequence throws an exception. This is because the result of the first new has not yet been committed to its std::unique_ptr object that would protect it from leaking

Each function argument is evaluated in its entirety before the next argument (there is no 'interleaving' of evaluation). Therefore, only the first two possible evaluation orders are possible, making this function call exception-safe

See also Expression Evaluation Order

Inline Functions

A function may be declared as inline. For example;-

inline int fn(int p);

It is much more common for an inline function to just be defined (often, but not necessarily, within a header file). This ensures that at the point of use, the definition is known and so actual inlining is more likely

The historical meaning of inline is as a hint to the compiler that the function body should be placed in-situ at the point the function is called, rather than instantiating the function once and performing a normal function call to it

A function that is declared inline takes-on certain characteristics;-

Actual inlining of a function (ie, substituting calls to it with a copy of the function) is left to the implementation to decide. Indeed, the existence (or absence) of the inline specifier generally has no effect on an implementation's decision to inline (or not) a function

In summary, apply inline to those functions that are known to be truly trivial, and are called frequently. Treat everything else as an optimisation (that in most cases will make virtually no difference to the total performance, and is likely to be ignored by the compiler anyway!). Beware of bloat

Constant Expression Functions

If a function is declared as a constexpr then as long as its arguments are also constexpr and it only uses constexpr internally (which, by definition, it MUST), then it can be invoked and its value determined at compile-time. See constant expressions

Function Invocation

A function is invoked thus;-

return-value = expression(arguments)

When a function is called, a new stack-frame is created. Formal arguments are allocated within this and are initialised from the function's actual arguments. Local variables are also created within the function's stack-frame

Returning From A Function

A function is normally exited with the return statement;-

// Returning from a function with a void return type return; // Returning from a function with a non-void return type return expression;

There are actually five ways to exit a function;-

Returning an object from a function, rather than writing to it via an argument reference, is often not as expensive as it looks on the surface. move rather than copy operations are used wherever possible so if the returned object is a container/handler then it can be passed-by-value back to the caller relatively inexpensively

Some functions, notably many operators, return references

However, returning (and then using) a pointer or reference to an object that was created within the function on the stack will result in undefined behaviour as all such objects are destroyed when the function exits

Return Value Mechanics

Consider the following;-

class X { /* ... */ }; X fn() { return X{}; } // Invoke fn() X a = fn();

The logical sequence of operations of the above example is as follows;-

In practice, a compiler will typically optimise-away all the temporary objects and, via RVO, initialise the variable a directly. However, the logical sequence must still be adhered to (because it may not always be possible to elide all of the move operations)

Any copy/move elision occurs even if the copy/move constructor would create side-effects (for example, logging debug information). For this reason, do not rely on copy and move constructors to be executed in any particular case; they may not be!

If the move constructor is deleted by redefining X as follows;-

class X { // Delete the move constructor X(const X&&) = delete; };

…then the program will fail to compile, with an error relating to the deleted move constructor

One consequence of this is that it is not possible to return a non-movable object by-value from a function. However, it is possible to achieve a similar effect by changing the above example to the following;-

class X { // Delete the move constructor X(const X&&) = delete; }; X fn() { return {}; } // Invoke fn() X&& a = fn();
Behaves similarly to C++11/C++14, except that because C++98 does not support move constructors, it uses copy construction instead. The effect of rendering the copy constructor inaccessible (by defining it private) would yield a similar compiler error to that described above

The logical sequence of operations of the above (first) example is as follows;-

In reality, when the call to fn() is made, a reference to the variable a is also passed to it. The abject can therefore be initialised in-situ

return {}

The return {arguments} form returns an initialiser rather than an object. For example;-

class X { // Constructor X(int a, float b); }; X fn() { return {42, 3.14}; } // Invoke fn() X a = fn();

Return Value Optimisation (RVO)

Argument Passing

Function arguments may be passed by value, or by reference. Pointers are passed by value and explicitly dereferenced within the function. For example;-

// a is passed by value, b is passed by reference and c is a pointer void fn(int a, int& b, int* c) { ++a; // Local variable ++b; // Implicitly dereferenced ++(*c); // Explicitly dereferenced } // Use the function int x = 0; int y = 10; int z = 20; fn(x, y, &z); // At this point, x == 0, y == 11 and z == 21

Arguments may be passed as const to prevent the function modifying them;-

void fn(int a, const int b, const int* c) { ++a; // Ok ++b; // Error: b is const ++c; // Ok *c = 42; // Error: *c is const }

Arguments may also be qualified as volatile in addition to, or instead of const

Argument Passing By Value

An Analysis Of Pass-By-Value

(Consider pass-by-value for copyable arguments that are cheap to move and are always copied)

Example Consider a function that always makes a copy of its supplied argument(s);-

Argument Passing By Reference

In accordance with the rules for reference initialisation, a literal, constant, or a value that requires conversion can be passed as a const T& argument, but NOT as a (non-const) T&. Allowing conversions for a const T& argument ensures that it can be given exactly the same set of values as a (pass-by-value) T argument by passing the value in a temporary, if necessary

A function may also take rvalue references as arguments. The main use for these is in defining move-constructor, move-assignment operations, and forwarding functions. For example;-

// Three functions (overloaded) that take an lvalue reference, // a const lvalue reference and an rvalue reference respectively void fn(vector<int>& v); void fn(const vector<int>& v); void fn(vector<int>&& v); // Use the function vector<int> u1 {1, 2, 3, 4}; const vector<int> u2 {5, 6, 7, 8}; fn(u1); // Invokes fn(vector<int>&) fn(u2); // Invokes fn(const vector<int>&) fn(vector<int> {1, 2, 3, 4}); // Invokes fn(vector<int>&&)

For small objects (say, up to 4 words), it can be more efficient to pass-by-value. Accessing an object that has been passed-by-reference is almost always slower than accessing one that is passed-by-value so if the object is small and is accessed several times from within the function, then pass-by-value may be the faster method. This is very platform and compiler-dependant though

Non-const pass-by-reference can often be eliminated by using suitable move-constructor and move-assignment operations and returning the result in the standard way instead

Pass-by-pointer is useful when 'no object' (indicated by nullptr) is a valid option. Compared to non-const pass-by-reference, it is also more explicit even if 'no object' is not a requirement

Array Arguments

Passing an array as a function argument will implicitly pass a pointer to the start of the array; that is, an argument of type T[] will decay to the type T*. Therefore, the following are (mostly) equivalent;-

void fn(int* p); // p is a pointer. An array (or not) structure is not specified void fn(int a[]); // a is a pointer to an array of unspecified size void fn(int v[42]); // v is a pointer to an array (the number of elements is not enforced)

Passing an array pointer in such a way that the number of elements is enforced (the array size becomes part of the argument type) can be achieved with;-

// Function taking a pointer to an array of 4 elements void fn(char(*a)[4]) { auto sz = sizeof(*a); // Yields a value of 4 auto a0 = a[0][0]; // Yields a value of 'a' auto a1 = a[0][1]; // Yields a value of 'b' auto a2 = (*a)[2]; // Yields a value of 'c' auto pa = *a; // Yields a char* to a[0] auto a3 = pa[3]; // Yields a value of 'd' } // Use the function char b[4] = {'a', 'b', 'c', 'd'}; fn(&b); // Ok: Yields the values shown above char c[3] = {'e', 'f', 'g'}; fn(&c); // Error: Wrong number of elements

Expanding on this, an alias can be created for the array type ( though see this point);-

using X = char[4]; // ...OR... typedef char(X)[4]; // Function taking a pointer to an array of 4 elements void fn(X* a) { // Use 'a' exactly as in previous example } // Use the function exactly as in previous example or using the alias X d[4] = {'a', 'b', 'c', 'd'}; fn(&d);

Similarly, preservation of the array size can also be achieved by passing a reference;-

// Function taking a reference to an array of 4 elements void fn(char(&a)[4]); // Use the function char b[4] = {'a', 'b', 'c', 'd'}; fn(b); // Ok char c[3] = {'e', 'f', 'g'}; fn(c); // Error: Wrong number of elements

One use for the above technique is in templates where the number of elements must be deduced. For example;-

template<class T, int N> void fn(T(&a)[N]) { // N will be the number of elements }

See also this point and this

The following example demonstrates a nasty error that can occur when using "naked" arrays to maintain a hierarchy of class objects;-

class animal { /* ... */ }; class dog : public animal { /* ... */ }; fn(animal* p, int len) { // Iterate through an array of animal objects } dog dogs[8]; fn(dogs, 8);

The above will compile but will result in a catastrophic failure. What will happen is that the array dogs[8] shall be implicitly converted to a dog* and then implicitly to an animal* (because a dog is a type of animal) in the call to fn(). The problem is sizeof(dog) is not sizeof(animal) which has obvious implications when fn() iterates through the pointer p and tries to access *p. Using containers such as std::array can help avoid these problems

Be extremely wary of any interface of the form (T*, count); if T is a base class then the results can be fatal

List Arguments

A list (indicated with {}) may be passed as a function argument as long as the values in the list can be used to initialise the specified argument type. For example;-

// Function 1, first version (overloaded) template<class T> void f1(std::initializer_list<T> x); struct S { int a; string s; }; // Function 1, second version (overloaded) void f1(S x); // Function 2 template<class T, int N> void f2(T(&x)[N]); // Function 3 void f3(int x); // Use the above functions f1({1,2,3,4}); // T is int and the initializer_list has size() 4 f1({1,"Hello"}); // f1(S{1,"Hello"}) f2({1,2,3,4}); // T is int and N is 4 f3({1}); // f4(int{1}); // This is ambiguous. It could resolve to either version of f1() // As a result, it resolves to the version with the initializer_list f1({1}); // T is int and the initializer_list has size() 1

Variable Numbers Of Arguments

A variable number (and in some cases, type) of arguments may be expressed in three different ways;-

The first two methods are described elsewhere. Here is an example of the last method; the standard 'C' printf() function. This takes at least one parameter, a plain string reference. It may also have zero or more additional parameters;-

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

Within such a function, the variable arguments are accessed thus;-

int printf(const char* format, ...) { // Set up environment for accessing variable arguments va_list varg; // Specify the va_list and the last formal argument to va_start() va_start(varg, format); // Variable arguments are accessed like this (the type must be known!)... type v = va_arg(varg, type); // Subsequent calls to va_arg() will return the next argument(s) in turn // Cleanup va_end(varg); }

It is sometimes necessary to forward variadic parameters on to some other function as-is. This can be achieved if the function being called takes a va_list rather than ..., as follows;-

// Standard 'C' vprintf() int vprintf(const char* format, va_list args); int my_printf(const char* format, ...) { va_list varg; va_start(varg, format); vprintf(format, varg); va_end(varg); }

Avoid using va_list, va_start etc

The mechanism is not type-safe, is prone to error (even ignoring the type safety issues), and there are better methods available; function overloading, better argument choice, plus the options mentioned at the start of this section

This method may be unavoidable if interfacing to legacy or 'C' code though

Default Arguments

Default values may be specified for function arguments, with the default(s) being used in the event the caller does not provide a value for that argument. For example;-

// Declare function with (some) default argument values void fn(int a, int b = 2, string c = "Hello"); // Use the function fn(38, 42, "Boing"); // All function arguments are as specified fn(38, 42); // Equiv. to fn(38, 42, "Hello") fn(38); // Equiv. to fn(38, 2, "Hello")

Default argument values do not have to be literals. For example;-

// Declare function with (some) default argument values static string my_name; void fn(int a, int b = 2, string c = my_name); // Use the function my_name = "Bob"; fn(38, 42); // Equiv. to fn(38, 42, "Bob") or fn(38, 42, my_name)

The above example would fail if my_name were a non-static variable; such default assignment is not allowed

Overloaded Functions

Two or more functions declared within the same scope and with the same name, but different arguments are said to be "overloaded". This is useful if the functions conceptually perform the same task. For example;-

print(int a); print(string s); print(char* p, int len);

Which version of an overloaded function is called is controlled by the argument-dependent lookup mechanism

Argument-Dependent Name Lookup

When an overloaded function is called, the compiler determines which version of the function resolve to by comparing the type of each overloaded function with the caller's argument(s). The criteria for "best match" of each argument is as follows, in this order;-

In addition;-

If the function takes a single argument then the one with the "best match" is called. If the function takes multiple arguments then the called function is the one that has a "best match" for one of the arguments and a better or equal match for the others

If two functions match equally well, except for the const-ness and/or volatile-ness of their argument(s), then the non-const/volatile version shall be chosen over the const/volatile version unless the const-ness/volatile-ness of the supplied argument(s) dictates otherwise (for example, an already const reference)

If more than one function matches at the same level then the call is considered ambiguous and a compiler error is raised. One exception to this is if a templated function expands to give the exact same signature as a standard (non-template) function, then overload resolution will favour the standard function

This technique is referred to as Argument Dependent Name Lookup (ADL) or Koenig Lookup

Overloading On Forwarding References

Avoid overloading on forwarding references 

Consider two overloaded functions;-

// fn() taking a forwarding reference template<typename T> void fn(T&& a); 1 // fn() taking an int void fn(int a); 2

The above functions could be called with;-

X b{}; short c = 3; fn(b); // Ok: Calls 1 fn(3); // Ok: Calls 2 fn(c); // Error?: Calls 1 rather than 2 - probably not what was wanted

In the last case, fn(T&& a) was a better match than fn(int a) as the latter would require a promotion of the supplied argument

Alternatives to overloading on forwarding references

Possiible options are;-

None of the above alternatives allows perfect forwarding within the function (for that, one must use a forwarding reference). If this is a requirement then one solution is to use Tag Dispatch. Expanding on the above example;-

// Implementation of fn() taking a forwarding reference template<typename T> void fn_impl(T&& a, std::false_type); // Implementation of fn() taking an int void fn_impl(int a, std::true_type); // fn() taking a forwarding reference template<typename T> void fn(T&& a) { fn_impl(std::forward<T>(a), std::is_integral<typename std::remove_reference<T>::type>()); }

Function Pointers

It is possible to take the address of a function and assign it to a pointer in the same way as for an object. Two function pointer forms are supported;-

rtn-type (*fp)(args) // 'fp' is the pointer variable's name rtn-type fp(args)

The function pointer may be used to call the function. For example;-

// Define a function void fn(int a, string b); // Define a pointer of the appropriate type to hold the function's address void (*fn_p)(int, string); // Call the function fn_p = &fn; fn_p(42, "Hello"); (*fn_p)(83, "Goodbye");

A function pointer alias is defined like this (using the above example);-

// Define a function pointer type using fp = void (*)(int, string); // Use the type fp fn_p = &fn;

…or using typedef;-

// Define a function pointer type typedef void (*fp)(int, string);

A function pointer may refer to a noexcept function;-

// Define a function void fn(int a, string b) noexcept; // Define and initialise a pointer of the appropriate type to hold the function's address void (*fn_p)(int, string) noexcept = fn;

Example of a function taking a function pointer;-

void fn(int (*fnp)(double)) { int v = fnp(1.23); } // ...or using the alternate form... void fn(int fnp(double)) { int v = fnp(1.23); }


A coroutine is a function whos execution can be suspended and resumed again at some future time. It is defined in the same way as a normal function or function template but its operation is quite different

In order to support the suspention and resumption operations, a coroutine requires its context to be recorded somewhere. It also uses a special mechanism for returning value(s) to the caller. As a result, the execution of a coroutine requires the following components to be defined;-

The Coroutine Function

What makes a function a coroutine, apart from the way it is used, is the presense of one or more of the following statements within the function body;-

co_await expr Suspend the coroutine until resumed again
co_return expr Return a value to the caller and then exit the coroutine. Attempting to resume the coroutine again results in undefined behaviour
co_yield expr Return a value to the caller and then suspend the coroutine until resumed again

The Coroutine Promise

The coroutine promise is a struct or class. Its members are defined by std::coroutine_traits, but its definition is left to the application (though there are some standard promise types defined). The promise maintains the value returned from the coroutine. It may also convey any exceptions ### thrown by the coroutine

The Coroutine Context


The interraction of the various components that make-up a coroutine is rather complex, and (maybe) best illustrated with an example. The following implements a "generator" coroutine; that is, a coroutine that generates successive values each time it is invoked

#include #include // Required for std::unique_ptr // The context. 'T' is the type that shall be returned back from the coroutine to the caller template<typename T> struct Generator { // The promise struct promise_type { // A 'staging' point for returning the value from the coroutine T current_value {}; // Constructor and destructor. In this case, these may be set to '= default' promise_type() = default; 1 ~promise_type() = default; 2 // Automatically called immediately before the body of the function coroutine // (in this example, get_next()) is executed std::suspend_always initial_suspend() 3 { return {}; } // Automatically called on execution of a co_return command, and after first calling return_value() std::suspend_always final_suspend() 4 { return {}; } auto get_return_object() 5 { return Generator{handle_type::from_promise(*this)}; } // Automatically called on execution of a co_yield command std::suspend_always yield_value(int value) 6 { current_value = value; return {}; } // Automatically called on execution of a co_return command, and prior to calling final_suspend() std::suspend_always return_value(int value) 7 { current_value = value; return {}; } // Automatically called if the coroutine throws an exception void unhandled_exception() { std::exit(1); } }; // The 'promise' handle type using handle_type = std::coroutine_handle<promise_type>; // The 'promise' object handle_type coro; // ##### std::unique_ptr<T> value; // Constructor and destructor Generator(handle_type h) : coro(h) {} 8 ~Generator() 9 { if (coro) { coro.destroy(); } } // Delete copy operators Generator(const Generator&) = delete; Generator& operator=(const Generator&) = delete; // Define move() operator Generator&& operator=(Generator&& a) { coro = a.coro; a.coro = nullptr; return *this; } // Return the next value generated by the coroutine int get_next_value() 10 { coro.resume(); 11 return coro.promise().current_value; } }; // The coroutine Generator<int> get_next(int start = 10, int step = 10) 12 { 12A auto value = start; for (int i = 0;; ++i) { if (value < 40) { 13 co_yield value; } else { 14 co_return value; } 15 value += step; } } // Use the coroutine void fn() { 16 auto gen = get_next(); 17 for (int i = 0; i < 4; ++i) { 18 auto val = gen.get_next_value(); 19 } 20 }

With reference to the above example, note the following;-

The execution sequence of the above example is as follows;-

Staring with a call to fn(), the sequence of operations of the above example will be as follows;-

Execution starts at 16. The coroutine get_next() is called 12

Invocation of the coroutine will cause the following to happen;-

Execution is returned to the point immediately after the call to get_next() 17. Note that at this point, the body of the coroutine has not executed

The loop in function fn() is executed, and a call is made to gen.get_next_value() 18

On calling gen.get_next_value(), the following happens;-

The loop in function fn() cycles round and another call is made to gen.get_next_value() 18

On calling gen.get_next_value(), the following happens;-

The loop in function fn() cycles round and another call is made to gen.get_next_value() 18. The above is repeated (10, 15, 13, 6, 11, 19) and sets the variable val to 30

The loop in function fn() cycles round and another call is made to gen.get_next_value() 18. 10 and 15 are repeated and then;-

The function fn() exits 20

The Generator destructor is called 9, which in turn also calls the coro destructor 2


Lambda Expressions

A lambda expression facilitates the definition of an anonymous function object (though it can also be named). It is a shorthand to the notion of defining a class with an operator(), making an object of that type and then invoking it. Lambda expressions may be passed to functions as an operation for the function to execute

A lambda expression is defined like this;-

auto lambda-name = [capture-list] <t-param-list> (argument-list) mutable constexpr consteval noexcept [[attributes]] -> type requires {body} auto lambda-name = [capture-list] <t-param-list> (argument-list) mutable constexpr consteval noexcept [[attributes]] requires {body} auto lambda-name = [capture-list] mutable constexpr consteval noexcept [[attributes]] requires {body} // See note below

All lambda expressions consist of the following components;-

It is possible to emulate init capture in C++11 using std::bind;-

auto px = std::make_unique<X>(); auto fn = std::bind([](const std::unique_ptr<X>& px) { /* ...use px... */}, std::move(px));

This works because std::bind move-constructs any of its members initialised from rvalues (which is exactly what std::move() produces). The lambda expression takes an lvalue reference to the captured pointer px. Note that it does not take an rvalue reference because although the initialisation value (returned from std::move()) is an rvalue, the member inside the bind obj