Igor's C++ Grimoire

27/05/2020

Contents

Introduction

Igor's C++ Grimoire aims to be a reasonably complete reference to C++11, C++14, and C++17

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

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 , and 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 . A few non-standard (but commonly supported) features are described, and these are identified with

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

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

Code snippets are shown like this; int a = fn();

Individual code elements are shown like this; a

Keywords are often shown like this; virtual

If a particular part of the syntax is being highlighted or referred to, it is shown like this; class colour {…}

Any optional parts of the syntax (or parts that may not be applicable in all cases) are indicated like this; short int a;

Pseudocode or syntax which may represent a number of forms is shown like this; while( condition ) {…}

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

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;- C Quick link to list of contents I 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

C++ is written in plain ASCII text and any one program is implemented as one or more text files

Whitespace

Whitespace (' ', TAB, and NEWLINE) must be used to separate type/variable/function names and reserved keywords. It is generally not required between names/keywords and syntactic elements and operators such as ( or ->. It is significant within string literals. Some operators are composed of multiple characters such as +=. Adding whitespace to these (ie, + =) would result in a syntax error. Other than these points and the occasional special case, whitespace may be freely used (or not) as required

Trigraphs

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

Trigraph Represents ??= # ??! | ??/ \ ??' ^ ??( [ ??) ] ??< { ??> } ??- ~

Trigraphs were first introduced into C to address the problem that some keyboard layouts and some character sets (in particular ISO-646) do not support all the characters of the C (and by extension, C++) syntax

Trigraph sequences are searched for, and replaced with their single-character equivalents by the pre-processor before any other parsing takes place

A trigraph sequence may occur anywhere within the C++ source text; the search-and-replace process takes no regard of the context. Therefore, a trigraph sequence may occur within a pre-processor directive, a comment, a character or string literal, etc

Trigraphs are now largely anachronistic and their use is strongly discouraged. Some pre-processors will not even parse them without a special option being specified. Some pre-processors will parse them but issue a warning

Support for trigraphs has been removed

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

Digraphs

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

Trigraph Represents <: [ :> ] <% ( %> ) %: #

Digraphs were introduced as a simplified alternative to the most common five trigraphs

The pre-processor performs the substitution of digraphs in the same way as for trigraphs

Unlike trigraphs, a digraph may not be used as part of another token. That is, a digraph sequence embedded within a pre-processor directive, a comment, a character or string literal, etc shall not be substituted. This makes them marginally safer

be substituted. This makes them marginally safer The digraph sequence %:%: is (specially) treated as a single token (##) and not as two separate # tokens

is (specially) treated as a single token (##) and not as two separate tokens Most pre-processors will honour and substitute digraph sequences but their use is strongly discouraged

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

By convention, implementation files have the filename extension .cpp and header files have the filename extension .h . Other options in use are .cxx , .cc , .hh , .hpp , etc

files have the filename extension and header files have the filename extension . Other options in use are , , , , etc Although the #include directive may be used almost anywhere, it is not wise to use it anywhere other than at the top of a file and from within the global namespace. A possible exception is from within a namespace when dealing with old code. Including implementation files into other implementation files is extremely bad practice

files into other files is extremely bad practice Because each implementation file is dealt with individually, each must define and include all that it needs in order to be analysed and compiled in isolation

file is dealt with individually, each must define and include all that it needs in order to be analysed and compiled in isolation A translation unit that relies on a definition in some other translation unit is said to have external linkage

A name that is not accessible from other translation units is said to have internal linkage

To be correct, all non-local declarations and definitions must be identical across all translation units; if two translation units had a different idea of a particular class structure, for example, then the resulting program would almost certainly be incorrect

declarations and definitions must be identical across all translation units; if two translation units had a different idea of a particular class structure, for example, then the resulting program would almost certainly be incorrect The way to ensure consistency between translation units is to define common header files and include them into any other files that make use of the definitions defined within them

A header file must (should!) never contain any function or object definitions (ie, anything that will directly consume memory at run-time), any unnamed namespaces or any using-directives. If a header file included a function or object definition, then that object would risk being instantiated multiple times which would either cause a subsequent linker failure or would cause the program to malfunction at run time

Exceptions to this are the definition of inline functions, template definitions, constants and constant expressions, either stand-alone or as class methods. Also, despite being definitions, struct and class types are also acceptable in header files

Where multiple header files are used that include each other, repeated inclusion of the same header can occur. Apart from wasting compilation time, this can cause problems with multiple definitions of the same thing(s). To protect against this, use something like this (see also pre-processor);- #ifndef MY_HEADER_H #define MY_HEADER_H (1) #endif

A compiler is free to remove from the final object/executable any functions that are not actually used, with the exception of virtual functions

The physical structure of the program, as defined by its collection of files, is not necessarily (and quite often is not) the same as its logical structure. For example, the logical structure of a namespace or class could be divided across multiple files

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 class header file is likely to include other header files in order to provide the types and values used in its exposed implementation details. These 'secondary' header files (which may include yet other header files) are therefore also brought into the client code, thus widening the dependency issue

If any change is made to any of these header files, even those that the client has no direct interest in, then the change ripples through to all users of the class, often resulting in all client code needing to be recompiled The basic principle that leads to reduced compilation dependency is to replace definitions with declarations wherever possible. To achieve this;- One option is to define a reference or pointer to an external type, rather than embed an object of that type. The former only requires a declaration, whereas the latter requires the full definition Ultimately, this approach leads to a pimpl (pointer-to-implementation - see also special member functions) structure for the class with the class becoming just a handle. This way, the exposed details are considerably reduced, ideally to a reference to a single implementation type for which a minimalist forward declaration only need be provided. The implementation details do not have to be included in the header and therefore clients of the class are no longer dependent on them, and are not directly effected by changes to them. This also has the additional benefit of truly hiding the implementation details from the client. To complete the picture, the class member functions would typically forward to (possibly other member) functions that operate on the (hidden) implementation type

Another approach is to define an interface-only abstract base class. Such a type typically declares a virtual destructor, and a set of pure virtual interface functions that specify the interface. It generally does not define any data members or any constructors. Non-virtual functions may be defined in the base class to implement common functionality. See also Alternatives to virtual functions Factory functions can be defined for each type of class derived from the interface-only base, and used like this;- X : public intf_only_base { public: X( args ); ~X(); }; std::unique_ptr<X> create_X( args ) { return std::unique_ptr(new X{ args }); } auto a{create_X( init-values )); An application operating on objects derived from such an interface-only base class would only require recompilation if the interface changed; changes to object definitions derived from the interface-only class would not ripple-through to the application code Another example shows how to use an interface-only base class using multiple inheritance

Neither pimpl classes or interface-only classes come without cost. They both make use of heap memory allocation, both employ a level of indirection (via a pointer in the first case, and via virtual functions in the second). This also prevents most inlining that may have otherwise been possible and desirable

classes or classes come without cost. They both make use of heap memory allocation, both employ a level of indirection (via a pointer in the first case, and via functions in the second). This also prevents most inlining that may have otherwise been possible and desirable Remember that a function may be declared with argument and return types that are only declared and not defined This moves the requirement to include the definition of the passed type from the header file that declares the function to the client code that calls the function. This is particularly useful if a header file declares many functions, of which only a few will be called in any one context

Header files need to be provided in pairs; one for declarations and one for definitions, with the latter including the former to ensure consistency between them. An example of this is the standard iostream library; definitions are provided in the header files <iostream> , <sstream> etc, with forward declarations being provided in <iosfwd>

, etc, with forward declarations being provided in As the standard library header <iosfwd> shows, this principle applies to templates as well as non-templates. Some implementations do not require template definitions at compile-time (they can perform instantiation at link-time), making this technique as useful for template types as it is for non-template types

Template Code Organisation

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

A template must be defined (not just declared) in any translation unit it is used in, though it need not be instantiated prior to use

A template must be declared before it is used, but may be defined afterwards (within the same translation unit)

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>

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> #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

The linkage rules for a template are those of the generated specialisations/instantiations

are those of the generated specialisations/instantiations Non- inline , non- template functions and static members must have a unique definition in exactly one translation unit within the whole executable. Such members can be a cause of linkage problems if defined within a class template that is used by several translation units

, non- functions and members must have a unique definition in exactly one translation unit within the whole executable. Such members can be a cause of linkage problems if defined within a class template that is used by several translation units Because templates tend to include more data in their .h files than their non- template equivalents, changes in a template can lead to considerable recompilation of many translation units, especially if the "include everywhere" technique is employed

files than their non- equivalents, changes in a can lead to considerable recompilation of many translation units, especially if the technique is employed One technique for minimising such dependencies is to add a non- template wrapper (function) around the template . Such a function can reside in a separate translation unit and it may even be possible to restrict the exposure of the template to the single .cpp file where the wrapper is defined

wrapper (function) around the . Such a function can reside in a separate translation unit and it may even be possible to restrict the exposure of the to the single file where the wrapper is defined Manual/explicit instantiation can also be employed as an alternative to "include everywhere" technique by limiting instantiation to a single translation unit. This avoids generating multiple instantiations in the first place

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

Header Description Ref. 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 ) Containers <array> array <bitset> bitset <deque> deque <forward_list> forward_list <list> list <map> map , multimap <queue> queue , priority_queue <set> set , multiset <stack> stack <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 <ostream> ostream <streambuf> streambuf <iostream> cin , cout , cerr , clog <fstream> ifstream , fstream , ofstream , filebuf <sstream> istringstream , stringstream , ostringstream , stringbuf Filesystem <filesystem> File system interface Concurrency <atomic> atomic , atomic_flag , memory_order <condition_variable> condition_variable , condition_variable_any , cv_status <future> future <mutex> mutex <thread> thread , this_thread Miscellaneous <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 <iterator> Iterators <limits> Numeric limits <locale> Localization <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 <ratio> ratio <regex> Regular Expressions <stdexcept> Standard exception types <string> string , u16string , u32string , wstring <system_error> System errors <tuple> tuple <typeindex> type_index <typeinfo> Type information <type_traits> type_traits <utility> Utilities; pair , relational operators, rvalue handling ( forward , move , move_if_noexcept , etc), swap <valarray> valarray - supports arrays of numeric values

The C Library is based on C99 The C Library is based on C11 with the 2012 technical corrections applied, except it does not include stdatomic.h, stdnoreturn.h or threads.h as these features are provided by other library components

An implementation may treat standard headers specially by providing some optimisation of their use. It is even possible that the header files do not actually exist at all as files, but just as a compiler's internal representation. Such optimisation should be transparent to the user's code though

'C' Functions

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

"C" int fn(void); externint fn(void);

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

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

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 #ifdef __cplusplus } #endif int a; void fn2(int a);

The "C" identifier indicates a linkage convention, not a language. Hence, the same syntax may also be used when a 'C' function needs to call a C++ function, or to access external code written in other languages such as assembler or Fortran; the "C" identifier remains the same

identifier indicates a linkage convention, not a language. Hence, the same syntax may also be used when a 'C' function needs to call a C++ function, or to access external code written in other languages such as assembler or Fortran; the identifier remains the same A function specified as extern "C" still obeys the C++ type-checking and argument conversion rules, and not the weaker plain 'C' rules

still obeys the C++ type-checking and argument conversion rules, and not the weaker plain 'C' rules Despite the extern syntax, any variables declared within an extern "C" {…} linkage block must themselves still be declared extern in order to be declarations rather than definitions

syntax, any variables declared within an linkage block must themselves still be declared in order to be declarations rather than definitions If a function is declared as extern "C" , the linkage specifier effects not only the function name, but also all of its arguments

, the linkage specifier effects not only the function name, but also all of its arguments As a result, it is not possible (unless a particular compiler and environment allows it) to (say) pass a pointer to a function with C++ linkage as an argument to a function with plain 'C' linkage. The reverse is also true

The C++ versions of the standard 'C' headers already include appropriate extern "C" specifiers

specifiers Any variables passed to or from a 'C' function must (of course) comply with the rules of the 'C' language. Therefore, any function arguments (or indeed any shared global variables) are restricted to POD types

Inline Assembler

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

asm( string );

asm is not guaranteed to be supported by an implementation

is not guaranteed to be supported by an implementation If it is supported then because of the nature of what it does, the exact details of what string means, its format and how it interfaces with the surrounding C++ code is highly platform-specific

means, its format and how it interfaces with the surrounding C++ code is highly platform-specific Although string is usually a ' \0 '-terminated ('C'-style) string literal, a particular implementation may define it otherwise

main()

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(); int main(int argc, char* argv[]); int main(int argc, char* argv[], char* envp[]);

Where;-

argc indicates the number of parameters being passed via the argv array. This is always ≥ 1

indicates the number of parameters being passed via the array. This is always ≥ 1 argv is an array of string pointers to ' \0 '-terminated strings. Generally, the first parameter ( argv[0] ) is the name of the application (as specified on the command line). There may be zero or more additional parameters that are (of course) application-specific

is an array of string pointers to ' '-terminated strings. Generally, the first parameter ( ) is the name of the application (as specified on the command line). There may be zero or more additional parameters that are (of course) application-specific The list of arguments is zero terminated, so argv[argc] is always nullptr

is always The form of main() with the third argument envp[] is non-standard but quite widely supported. The envp[] argument is an array of string pointers, arranged in the same way as the argv[] argument. It that refers to the environment (variables) that the program is running in and can be used in the same way as the POSIX extern char** environ reference

The form of with the third argument is non-standard but quite widely supported. The argument is an array of string pointers, arranged in the same way as the argument. It that refers to the environment (variables) that the program is running in and can be used in the same way as the POSIX reference The normal function return requirements of main() are relaxed; there is no absolute requirement for main() to return a value. Reaching the end of main() without returning a value implies success. Convention is that if a value is returned, zero indicates 'success', and any other value indicates an application/platform-specific error code

are relaxed; there is no absolute requirement for to return a value. Reaching the end of without returning a value implies success. Convention is that if a value is returned, zero indicates 'success', and any other value indicates an application/platform-specific error code main() must be defined globally, outside of any namespace

Program Termination

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

If main() returns

If there is a call to exit(int rtn_code) . This represents normal termination, and destructors for all global and static objects (but not local) shall be called. Any open 'C' file streams shall be flushed and closed. Temporary files shall be deleted (ref. tmpfile() ). The value passed to exit() is analogous to the return value from main() . Calling exit() from within a destructor may cause an infinite loop

. This represents normal termination, and destructors for all global and objects (but not local) shall be called. Any open 'C' file streams shall be flushed and closed. Temporary files shall be deleted (ref. ). The value passed to is analogous to the return value from . Calling from within a destructor may cause an infinite loop If there is a call to abort() . This indicates abnormal termination. No destructors are called

. This indicates abnormal termination. No destructors are called If an exception is thrown but not caught

By violating noexcept. See also unexpected() and set_unexpected()

If there is a call to quick_exit() . This is the same as exit() and also represents normal termination, but no destructors are called

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;-

void my_cleanup(); if ( !atexit( &my_cleanup ) ) { } else { }

The function at_quick_exit() performs the same job as atexit() but for calls to quick_exit()

performs the same job as but for calls to An object constructed before a call of atexit(f) will have its destructor called after f() is invoked. An object constructed after a call of atexit(f) will have its destructor called before f() is invoked (ie, destruction in the reverse order of construction is maintained)

will have its destructor called after f() is invoked. An object constructed after a call of will have its destructor called before f() is invoked (ie, destruction in the reverse order of construction is maintained) exit() , abort() , quick_exit() , atexit() , and at_quick_exit() are defined in <cstdlib>

, , , , and are defined in Abnormal termination can also result in an implicit call to std::terminate()

Pre-Processor

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

The preprocessor has no concept of a type; it deals only with numeric literals and strings (any surrounding " … " are part of the string as far as the preprocessor is concerned)

… are part of the string as far as the preprocessor is concerned) It can perform some basic arithmetic (eg, + , - , * and / ), logical (eg, && , || ), bitwise (eg, & , | ) and comparative (eg, == , < ) operations

, , and ), logical (eg, , ), bitwise (eg, , ) and comparative (eg, , ) operations It does not understand what a C++ variable, type or function is; specifying any of these within a pre-processor directive shall be treated as an arbitrary string without specific meaning

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

Directive Description #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 "./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) ) FN_CALL(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;- #Hello-World …would yield;- "Hello-World" symbol ## symbol Concatenates two symbols. For example;- Hello- ## World …would yield;- Hello-World #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 to its 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 text 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;- #define USE_EXPERIMENTAL_FEATURE (1) #if (USE_EXPERIMENTAL_FEATURE == 1) #endif

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

Macro Description __cplusplus Indication of C++ compilation (rather than plain 'C'). Its value is 201103L / 201402L / 201703L . See also __STDC__ __DATE__ Current date in the format Mm dd yyyy __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;-

Macro Description __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

The /* */ comment style cannot be nested within itself; doing so will result in a compilation error

comment style cannot be nested within itself; doing so will result in a compilation error The // comment style can be nested inside itself (on a single line) or within a /* */ style 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

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);-

Atributes do not affect the type system and they do not change the meaning of a program. They are instructions to the compiler to enable or disable certain features. The compiler may issue appropriate warnings based on the existence (or absence) of a perticular attribute

A compiler will not issue an error for any attributes it doesn't recognise (though it may issue a warning). Implementations often exhibit this behaviour anyway

A compiler will not issue an error for any attributes it doesn't recognise (though it may issue a warning). Implementations often exhibit this behaviour anyway Some attributes take arguments, such as [[deprecated("reason text")]]

Multiple attributes may be specified seperately such as [[deprecated]] [[noreturn]] or together as a comma-separated list, [[deprecated, noreturn]]

or together as a comma-separated list, Attributes applied to a declaration may be specified before and/or immediately after the entity's name

Attributes applied to a non-declaration must usually be placed immediately after the entity

An implementation may define local (non-standard) attributes. If it does then these should be defined within a unique namespace. For example, [[my_compiler::turbo_boost]]

If several attributes from the same namespace are required, then a using directive may be used. For example, [[my_platform::turbo_boost, my_platform::flash_lights]] could also be written as [[ using my_platform: turbo_boost, flash_lights]]

If several attributes from the same namespace are required, then a directive may be used. For example, could also be written as Several implementation-specific methods of specifying attributes are also in common use, such as __attribute__( attr_name )

[[deprecated]]

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;-

Application Example Class declaration 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); 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 template<typename T> class X { }; template<> class [[deprecated]] X<int> { };

Note the somewhat inconsistent placement of the [[deprecated]] attribute, depending on the target entity. The indicated placement is important

Note the somewhat inconsistent placement of the attribute, depending on the target entity. The indicated placement is important A name declared as non-deprecated may be later redeclared deprecated but not visa-versa

[[maybe_unused]]

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

[[nodiscard]]

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 };

Names

A Name refers any of the following;-

An identifier—that is, a variable; given the variable declaration int a ; , a is the variable name. A function; given the function declaration void fn (int a); , fn is the function's name

, is the variable name. A function; given the function declaration , is the function's name An operator function has a name. Given the function declaration X& operator+ (const X& rhs); , operator+ is a name

, is a name Similarly for a conversion operators, for example operator int (const X& a); , and user-defined literal operators, for example operator"" _cm ();

, and user-defined literal operators, for example A template. Given;- template<typename T> struct X { T a; }; X is the template's name

A goto label

A name is an lvalue expression

Naming Rules

Note:

Names

A name starts with a character a-z, A-Z, or '_', and consists of these plus 0-9

In the case of an operator @ name, then the appropriate operator character(s) are also used

name, then the appropriate operator character(s) are also used All names (whether namespaces, types, variables, functions, etc) are case sensitive

Non-local names starting with '_' are reserved for use by the environment and should not be used. All names starting with '_{A-Z}' or '__' are reserved

C++ imposes no limit on name length, though other parts of the tool chain are likely to

Scope

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;-

Scope Details Local A 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 Class A ('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 Namespace A ('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 Global A ('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 Statement A '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 Function A 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)

The same type name or the same non-type name may not be defined twice within the same scope. So;- void fn(int a) { int a; } class b {}; enum b { one, two };

It is possible to define a type name that is the same as a non-type name in the same scope. For example, this is legal;- namespace x { class a {}; void a(void); }

When referring to a name that is duplicated in this way, the default is to assume use of the non-type name. To use the type version of the name, it must be qualified by preceding its use with the appropriate struct, class, enum or union. This is called an Elaborated Type Specifier. Expanding on the previous example;- int main() { x::a b; class x::a c; }

A forward declaration is a special case of an Elaborated Type Specifier

Name Hiding And Qualification

A declaration of a type or non-type name will hide/override any same-named declaration already made in a wider scope until the (narrower) scope ends

or name will hide/override any same-named declaration already made in a wider scope until the (narrower) scope ends The same name may be defined in two different scopes. It may be necessary to disambiguate which of the names is being referred to by qualifying the reference. For example;- namespace x { int a; namespace z { int b; } } namespace y { int a; } using namespace x; using namespace y; int c = a; int d = x :: a; Qualification can be nested. Expanding on the above example;- int e = x :: z :: b; The qualification syntax (::) may be used to identify type or (as in the above example) non-type names.

A hidden global name may be referred to by qualifying with :: name

Namespaces

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… }

Members declared within a namespace form a distinct scope; separate from members of any other namespace

Members within a namespace may make reference to other members within the same namespace without any special syntax (they are in the same scope)

Access to namespace members from outside the namespace may be achieved by using an explicitly qualified name; namespace-name :: member-name

Global members (not declared in any namespace) may be explicitly qualified by using :: name

This example demonstrates the scope of namespaces and implicit/explicit name resolution;- struct X {int y; int z;} int var = 0; void fn(X& b); namespace my_library { struct X {int y; int z;} int var = 0; void fn(X& b) { b.y = var; } } namespace other_library { struct X {int y; int z;} int var = 0; void fn(X& b) { b.y = var; } void fn2( :: X& b) { b.y = :: var; } void fn3(my_library :: X& b) { b.y = my_library :: var; } void fn4() { :: X v1; :: fn(v1); my_library :: X v2; my_library :: fn(v2); X v3; fn(v3); } } X v1{}; fn(v1); my_library :: X v2{}; my_library :: fn(v2);

Argument-dependant lookup may be used to qualify name lookup, rather than explicit qualification with ::. For example;- my_library :: string s; my_library :: X v{}; fn(v); When performing such lookups, normal overload resolution is performed. This means that if a namespace member invokes a function, argument-dependant resolution will NOT prefer its own namespace over other namespaces A special case is when a class member invokes a function. In this case, argument-dependant resolution will prefer other members of the same class or base classes over members of any namespaces

Namespaces are open and may be declared in parts. This allows the namespace members to be declared in separate source files, or maybe to allow division of members for improved readability (for example, separating the private from the public interface). For example;- namespace my_namespace { void fn1(); } namespace my_namespace { string fn2(int); } Namespace alias' may not be used to 'add' to a namespace in this way; the original namespace-name must be used

Namespace members may be declared within the namespace but defined outside it by qualification. For example;- namespace my_namespace { void fn(); } void my_namespace :: fn() {…} This is an important feature; it allows the namespace to be declared in a header file and some or all of its members to be defined elsewhere

A namespace member must be declared within the namespace before it can be defined outside it

It is not possible to declare a new namespace member using the :: qualifier. All members must be declared within the namespace

qualifier. All members must be declared within the namespace A class definition creates a namespace, hence the :: qualifier is used in the same way for class member qualification

qualifier is used in the same way for class member qualification A variable defined within a namespace without an explicit initialiser shall be initialised by default in the same way as globals are

When used in a namespace, static has the same effect as for globals

has the same effect as for globals It is legal to declare plain 'C' functions within a namespace without effecting the external linkage. This technique is used in the standard library <cstdio> header for example and is useful for including plain 'C' libraries without polluting the global namespace

header for example and is useful for including plain 'C' libraries without polluting the global namespace Namespace names follow the usual naming rules

The [[deprecated]] attribute may be applied to a namespace

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 { void 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(); }

Nested namespaces are subject to the same qualification and resolution rules as other namespaces

Inline Namespaces

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

namespace ver3 { void fn(); }

inline namespace ver4 { void fn(); void fn2(); }

namespace my_library { #include "ver_3.h" #include "ver_4.h" } my_library::fn(); my_library::fn2(); my_library::ver3::fn(); my_library::ver4::fn(); my_library::ver4::fn2();

The main intended use of inline namespaces is, as the above example shows, to allow library implementations to cleanly support multiple versions of themselves

Unnamed (Anonymous) Namespaces

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

namespace { }

A nameless namespace is useful when the purpose is to just create a local scope to avoid clashes with other namespace members

Immediately following a nameless namespace is an implicit using-directive so that all the namespace members are externally visible from the parent scope, and into other scopes if qualified

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; string s;

The effects of the using -declaration are restricted to the current scope

-declaration are restricted to the current scope The using -declaration applies to all versions of an overloaded name

-declaration applies to all versions of an overloaded name A using -declaration creates a local name. Like any other local name, this may hide other non-local declarations of the same name

-declaration creates a local name. Like any other local name, this may hide other non-local declarations of the same name It is not possible to apply a using -declaration to an unnamed namespace member

-declaration to an unnamed namespace member The using -declaration may also be used within class hierarchies

-declaration may also be used within class hierarchies The using keyword may also be used in other contexts; see aliases and 'using' Directive

'using' Directive

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

using namespace std; string s; vector v;

Names brought into scope with a using -directive are on an equal priority with global names in terms of name resolution

-directive are on an equal priority with global names in terms of name resolution Name clashes between members of different namespaces brought into the same scope with using -directive (or between those namespace members and globals) are only considered to be in error if the clashing name(s) are actually used

-directive (or between those namespace members and globals) are only considered to be in error if the clashing name(s) are actually used See also aliases and 'using' Declaration

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; }

Following the above, it would be possible to access any members of the external_lib1 and external_lib2 namespaces, and selected parts of the external_lib3 namespace in terms of my_library . For example, if external_lib1::fn(); were defined, it would be possible to access it with my_library::fn();

and namespaces, and selected parts of the namespace in terms of . For example, if were defined, it would be possible to access it with When it comes to resolving clashes within compound namespaces, explicitly defined names and using -declarations take priority over using -directives. So if external_lib1::X were defined, then my_library::X would resolve to external_lib3::X and not external_lib1::X

-declarations take priority over -directives. So if were defined, then would resolve to and not The inline qualifier may be applied to a using-directive. In the event of a name clash, this will give priority to the effected namespace. For example;- inline using external_lib1; While this may have some use in specific cases such as version control, it is not something that should be used generally

The above example also shows that using -declarations and using -directives may be used for the same namespace. This is useful for resolving clashes; in this case, the whole of the extern_lib2 namespace is available but a reference to my_library::Z will resolve to external_lib2::Z in preference to any other declaration

-declarations and -directives may be used for the same namespace. This is useful for resolving clashes; in this case, the whole of the namespace is available but a reference to will resolve to in preference to any other declaration The only time the external names would have to be used would be during object declaration. For example, if a type external_lib1::Y were defined, then my_library::Y my_obj; would not work. external_lib1::Y my_obj; would have to be specified instead

Namespace Alias'

A namespace alias may be defined. This is usually used to provide a shorter, more convenient name for a namespace, or (for example) to provide a generic name for a versioned library;- namespace alias = namespace-name For example;- namespace my_very_long_namespace_name { …member declarations… } namespace stuff = my_very_long_namespace_name;

This idea can also be applied to individual namespace members (though through a different mechanism);- using object = my_very_long_namespace_name::long_object_name;

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

Many things can lead to Undefined Behaviour ; some obvious ones being dereferencing a null pointer, race conditions in a multi-threaded program, casting-away const on a variable and then writing to it, accessing an object beyond its lifetime, and many more

; some obvious ones being dereferencing a null pointer, race conditions in a multi-threaded program, casting-away on a variable and then writing to it, accessing an object beyond its lifetime, and many more If a program exhibits Undefined Behaviour then, essentially, anything that follows in the execution cannot be relied upon to be correct, or even to make sense. The program may crash. It may exit. It may continue to run but behave incorrectly or generate an indeterminate result. It may continue to run and operate perfectly well. It may proceed to calculate the answer to life, the universe, and everything; anything is possible. For example, the following will invoke Undefined Behaviour but is unlikely to cause a crash or provoke the program to become spontaneously self-aware. It will, however, generate an indeterminate result;- int a = 1; int b = --a + ++a;

An operation that exhibits Undefined Behaviour may behave differently on different platforms and architectures. It may even behave differently when compiled on different versions of the same compiler

may behave differently on different platforms and architectures. It may even behave differently when compiled on different versions of the same compiler There are some cases where the possibility of Undefined Behaviour can provide the compiler with optimisation possibilities; if certain behaviour should never happen (because to do so would be Undefined Behaviour ), the compiler may ignore such possibilities, resulting in better code generation; example

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

In many cases, Unspecified Behaviour must conform to one or more of a (typically small) selection of options. Such behaviour need not be documented by the implementation, and in many cases, is not

must conform to one or more of a (typically small) selection of options. Such behaviour need not be documented by the implementation, and in many cases, is not It may not degenerate into undefined behaviour

Unspecified Behaviour may or may not be consistent throughout the program

may or may not be consistent throughout the program Example: The order in which arguments to a function are evaluated is unspecified ; it is at the discretion of the implementation, and may differ between calls, even to the same function

The order in which arguments to a function are evaluated is ; it is at the discretion of the implementation, and may differ between calls, even to the same function Because Unspecified Behaviour may be inconsistent (even within the same program), code which relies on it should be avoided; it is safest to treat it similarly to undefined behaviour in this respect

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

Like unspecified behaviour , degradation to undefined behaviour is not an option for the implementation

, degradation to is not an option for the implementation Unlike unspecified behaviour , it must be consistent throughout the program

, it be consistent throughout the program Example: The size of an int type must conform to a minimum size, but it is left to the discretion of the implementation to define its actual size, which should be documented. Another example is how high-order bits of a signed integral are handled when a right-shift operation is performed on it

The size of an type must conform to a minimum size, but it is left to the discretion of the implementation to define its actual size, which should be documented. Another example is how high-order bits of a signed integral are handled when a right-shift operation is performed on it Implementation Defined Behaviour is, by its very definition, non-portable

Fundamental Data Types

Elsewhere, there are several references to Integral types. Unless specifically stated otherwise, this can be interpreted as referring to any of the 'Character', 'Integral', or 'Boolean' types in the following table, (with or without modifiers), as well as "plain" enumerations

types. Unless specifically stated otherwise, this can be interpreted as referring to any of the 'Character', 'Integral', or 'Boolean' types in the following table, (with or without modifiers), as well as "plain" enumerations An arithmetic type is an Integral or any of the 'Floating Point' types in the following table

type is an or any of the 'Floating Point' types in the following table A scalar type is one that is an arithmetic type, an enum class, a pointer, pointer-to-member, or std::nullptr_t (in each case, with or without modifiers)

type is one that is an type, an enum class, a pointer, pointer-to-member, or (in each case, with or without modifiers) There are also several references to Built-In types. Unless specifically stated otherwise, this can be interpreted as referring to any of the types in the following table, (with or without modifiers), and arrays of any of these. void is usually excluded from such contexts

types. Unless specifically stated otherwise, this can be interpreted as referring to any of the types in the following table, (with or without modifiers), and arrays of any of these. is usually excluded from such contexts All built-in types ( except void , including void ) are also literal types

types ( except , including ) are also literal types Although implemented as some fundamental type, an enumeration ("plain" or class ) is a user-defined type

) is a user-defined type There are a number of standard predicates that may be used to test the type properties referred to above

Type Family Guaranteed Minimum Data Size (bits) char Character 8 - may be signed or unsigned signed char Character 8 - signed unsigned char Character 8 - unsigned wchar_t Character Implementation-specific but ≤ sizeof( long ) char16_t Character 16 (for UTF-16) char32_t Character 32 (for UTF-32) short int Integral 16 - signed by default int Integral 16 (usually native arch. size) - signed by default unsigned Integral as int but unsigned long int Integral 32 - signed by default long long int Integral 64 - signed by default float Floating Point 32 double Floating Point 64 long double Floating Point 64 (see note below) bool Boolean Implementation-specific but ≤ sizeof( long ) void no data Implementation-specific - an incomplete type

A char is usually 8 bits, but this is not always the case. Regardless, sizeof( char ) is ALWAYS 1. See this point for further details

is usually 8 bits, but this is not always the case. Regardless, is 1. See this point for further details The Character types are large enough to represent at least 256 distinct values. This guarantees UTF-8 encoding capability

The types are large enough to represent at least 256 distinct values. This guarantees UTF-8 encoding capability There are a number of Data Models in widespread use (though an implementation is not guaranteed to follow any of these);- Data Model int long pointer Common Platform(s) LP32 or 2/4/4 16 bit 32 bit 32 bit Win16 API ILP32 or 4/4/4 32 bit 32 bit 32 bit Unix, BSD, Linux, Win32 API LLP64 or 4/4/8 32 bit 32 bit 64 bit Win64 API LP64 or 4/8/8 32 bit 64 bit 64 bit Unix, BSD, Linux ILP64 or 8/8/8 64 bit 64 bit 64 bit Cray, Unicos In all of the above Data Models, long long is 64 bit Generally, LP32 and ILP32 are considered 32 bit systems and LLP64, LP64 and ILP64 are considered 64 bit systems though this definition is somewhat controversial ILP64 is not very common; defining int as 64 bit tends to cause problems with portability, and it artificially inflates data structure sizes. It is common for implementations of this model to define a specific 32 bit integer type, often called _int32, but this is outside of the C++ specification

There are a number of in widespread use (though an implementation is not guaranteed to follow any of these);- long double is commonly either 64 or 80 bits (though could be larger). For > 64 bits, sizeof(long double) will almost certainly be rounded-up to 96 bits (12 bytes) or (even more likely) 128 bits (16 bytes) to satisfy hardware alignment requirements; this padding cannot be used for other purposes. See also this point

is commonly either 64 or 80 bits (though could be larger). For > 64 bits, will almost certainly be rounded-up to 96 bits (12 bytes) or (even more likely) 128 bits (16 bytes) to satisfy hardware alignment requirements; this padding cannot be used for other purposes. See also this point More formally, the following is guaranteed;- 1 ≡ sizeof(char) ≤ sizeof( short ) ≤ sizeof( int ) ≤ sizeof( long ) ≤ sizeof( long long ) 1 ≤ sizeof( bool ) ≤ sizeof( long ) sizeof( char ) ≤ sizeof( wchar_t ) ≤ sizeof( long ) sizeof( float ) ≤ sizeof( double ) ≤ sizeof( long double ) sizeof( T ) ≡ sizeof( signed T ) ≡ sizeof( unsigned T ) (where T is any Integral type that accepts modifiers)

The effect of assigning too large a value to a (signed) char is undefined behaviour

is Automatic type conversion is performed between fundamental types. Most significant bits are lost when converting from a large to a small type

char , signed char , and unsigned char are considered to be three distinct types, so you can't (say) mix pointers of each type. However, char must behave in exactly the same way as one of the other two types

, , and are considered to be three distinct types, so you can't (say) mix pointers of each type. However, must behave in exactly the same way as one of the other two types wchar_t size is implementation-specific and is large enough to hold the largest character set supported by the implementation's locale

size is and is large enough to hold the largest character set supported by the implementation's locale The header <cstdint> defines sized types (actually aliases) such as int64_t (exactly 64 bits), uint_fast16_t (the 'fastest' unsigned integer of at least 16 bits), and int_least32_t (at least 32 bits)

defines sized types (actually aliases) such as (exactly 64 bits), (the 'fastest' unsigned integer of at least 16 bits), and (at least 32 bits) Testing size assumptions within an application can be done by including <limits> and adding a compile-time test such as static_assert(sizeof(int) == 4, "int is not 32 bits");

and adding a compile-time test such as Min/max values can be obtained with (eg) std::numeric_limits<float>::max() . Signed representation can be checked with (eg) std::numeric_limits<char>::is_signed() . The standard header <climits> also defines a set of macros that specify minimum and maximum values for fundamental types

. Signed representation can be checked with (eg) . The standard header also defines a set of macros that specify minimum and maximum values for fundamental types Floating point type representation is defined by the IEEE-754 standard

standard Although void is syntactically a fundamental type, its use is restricted to building more complex types (ie void* ), and as a function argument or return type

is syntactically a fundamental type, its use is restricted to building more complex types (ie ), and as a function argument or return type Fundamental types never throw exceptions during initialisation or assignment

int vs unsigned int Whether to prefer (signed) int or unisgned 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. 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<unignsed 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

long double Because of changes in modern machine architectures (in particular x86), the size of long double is tending to move (possibly counter-intuitively) towards 64 bits

Fundamental Type Modifiers

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

Modifier Effect const volatile signed type varname Signed const volatile unsigned type varname Unsigned

Unsigned types can lead to performance penalties on some platforms

Regular Types

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

Can be default constructed, and destroyed

Can be copied, using assignment or initialisation with normal copy semantics

Can be moved, using assignment or initialisation with normal move semantics

Can be compared using == and !=

and Supports taking the address of it with &, and other "normally expected" behaviour. See also std::addressof()

All built-in types except void are regular

are Regular types are often assumed/required in many cases such as when using templates, especially standard library ones

are often assumed/required in many cases such as when using templates, especially standard library ones See also Plain Old Data (POD)

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)); } }; 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; constexpr int e = a.calc(d);

A literal type may be used to define an object who's value is known at compile-time

may be used to define an object who's value is known at compile-time A standard library type predicate is_literal_type<T>::value is defined in <type_traits> that returns whether a type is a literal type or not. This may be used in exactly the same way as is_pod<T> is_literal_type<> is deprecated; not considered useful

All built-in types, except void , are literal types

All built-in types, except , are All built-in types, including void , are literal types

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;-

It is default-defined (use = default if you need to define one)

Its class has no virtual functions

functions Its class has no virtual bases

bases All bases are also trivial

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;-

Has no virtual functions

Has no virtual bases

Has no members that are references

Has the same access specifier for all non-static data members

Has all non- static members declared in the same class (either all in the derived class or all in the same base class)

members declared in the same class (either all in the derived class or all in the same base class) Has no two base class objects of the same type

Has no base that is not also standard layout

For non-union types, have no base class sub-objects of the same type as the first non-static data member of the derived class (this is because of empty-base optimisation). This rule applies recursively throughout the class tree For union types, this rule extends to all members, not just the first

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;-

No user-provided constructors (explicitly deleted or defaulted constructors are allowed)

No inherited or explicit constructors

No inherited or explicit constructors No default member initialisers

No default member initialisers No private or protected non-static data members

non-static data members It may contain aggregate and/or non-aggregate members

No base classes

No base classes No private, protected or virtual base classes. Any allowed base classes may be aggregare and/or non-aggregate types

No private, protected or virtual base classes. Any allowed base classes may be aggregare and/or non-aggregate types No virtual functions

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);-

Type Description std::byte 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 std::size_t

size_t 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 std::max_align_t

max_align_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 std::ptrdiff_t

ptrdiff_t A signed integer large enough to hold the result of any pointer subtraction std::nullptr_t 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>;-

Type Description std::std::intptr_t

intptr_t

std::uintptr_t

uintptr_t 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 int8_t

int16_t

int32_t

int64_t 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 uint8_t

uint16_t

uint32_t

uint64_t 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 int_fast8_t

int_fast16_t

int_fast32_t

int_fast64_t 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 uint_fast8_t

uint_fast16_t

uint_fast32_t

uint_fast64_t 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 int_least8_t

int_least16_t

int_least32_t

int_least64_t 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 uint_least8_t

uint_least16_t

uint_least32_t

uint_least64_t 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 intmax_t Maximum sized signed integer type supported by the implementation Min and max values are indicated by the macros INTMAX_MIN and INTMAX_MAX respectively uintmax_t Maximum sized unsigned integer type supported by the implementation Max value is indicated by the macro UINTMAX_MAX. Min value is always zero Note that most of the above types are declared in the std namespace and in the global namespace as well. The respective definitions are identical

namespace and in the global namespace as well. The respective definitions are identical Generally, the above types work with respect to the size of a byte . However, a byte is not guaranteed to be 8 bits 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

void (possibly const and/or volatile qualified)

(possibly and/or qualified) A class that is declared (ie, forward declared) but not defined

that is declared (ie, forward declared) but not defined An array of unknown bounds

An array of elements that are incomplete types

An enum from the point of declaration up until its underlying type is determined

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); void fn(X* a) { fn2(a); }

User-defined Data Types

Declarator Operators

With the exception of the last three examples dealing with function declarations, the above operators may also be applied to auto . eg, const auto & a = b; or auto a [] = {1, 2, 3};

. eg, or The postfix operators bind tighter than the prefix ones, so char * cheese [] is an array of char pointers. The alternative char( * cheese) [] is a pointer to an array of char

is an array of pointers. The alternative is a pointer to an array of It is this binding rule that means we have to use parentheses to define a function pointer

Operators apply to individual names ONLY . This is why int * a, b; will result in a being an int pointer and b being an int (and not a pointer)

. This is why will result in being an pointer and being an (and not a pointer) See also Regular Types

Derived Types

Derived types may be formed from any of the fundamental types or from other derived types

A union is a type of struct which in turn is a type of 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; struct T2 {T1* a, T1* b} void fn(T1 a); struct T1 c; struct T1 {int y, int z}; struct T1 d;

Types that are forward declared in this way are incomplete types until fully defined

A type name becomes usable immediately after its name is specified; the entire type does not have to be parsed first as long as its use does not require its internal structure to be known. This allows (say) structures to contain pointers to the same type (such as in a linked list)

A forward declaration may be used as long as its use does not imply knowledge of the type's internals

Reference and pointer types may be defined for an incomplete type, but not objects (this would imply knowledge of the type's internals)

This principle is extended to all types, including classes, of which struct is a type-of

is a type-of A function declaration may take an argument of an incomplete type by value, reference or pointer, but a function definition may take such as argument by reference or pointer only (by-value would imply knowledge of the type's internals)

A Forward Declaration is a special case of an Elaborated Type Specifier

Deducing Types

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

Function Templates An argument may be passed by value and of type T An argument may be passed by reference or pointer and be of type T& or T* An argument may be a Forwarding References of type T&&

Class Templates constructor arguments These rules are the same as those used for function agument type deduction

Lambda implicit return types These use the same rules as function template by-value parameters

decltype() and decltype(auto) These use a separate set of rules

Lambda capture These rules are based on those of function template by-reference and by-pointer parameters, but are not identical

Lambda init capture These rules are the same as those used for auto objects

auto objects These rules are based on those of function template argument type deduction, but are not identical

Generic lambda auto parameters These rules are the same as those used for function template type deduction

Function auto return type These rules are the same as those used for function template type deduction



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;- template<typename T> class TD; The above could be used like this;- template<typename U> void fn(U& a) { TD<U> u_type; TD<decltype(a)> a_type; } 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

Here is a complete list of function template argument constructs from which it is possible to deduce a type T or U, and a non-type argument N;- T const T volatile T T * T & T [ constant-expression ] type [ N ] class-template-name < T > class-template-name < N > U < T > T < N > T <> T type ::* T T ::* type T ::* Function Pointer Forms;- T (*)( args ) type ( T ::*)( args ) T ( type ::*)( args ) type ( type ::*)( args-TN ) T ( T ::*)( args-TN ) type ( T ::*)( args-TN ) T ( type ::*)( args-TN ) type (*)( args-TN ) type is some other type not specified in the parameter list args is a parameter list that does not allow deduction args-TN is a parameter list from which T and/or N may be derived from successive application of the above rules

Implicit type conversions are not applied during template argument deduction; see how to deal with this

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;-

fn( expr );

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

If arg-type is a (non-forwarding) reference or a pointer;- If expr is a reference type then ignore the reference part Then pattern-match the type of expr against arg-type to determine T Function Decl. Call T arg-type Notes void fn( T & a) int b = 3;

fn(b); int int& const int c = 3;

fn(c); const int const int& const int& d = b;

fn(d); const int const int& const int e[] = {1, 2, 3};

fn(e); const int[3] const int (&)[3] #1 void f(int, double);

fn(f); void (int, double) void (&)(int, double) #2 void fn(const T & a) int b = 3;

fn(b); int const int& #3 const int c = 3;

fn(c); int const int& const int& d = b;

fn(d); int const int& void fn( T * a) int b = 3;

fn(&b); int int* const int* d = &b;

fn(d); const int const int*



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; } int x[] = {1, 2, 3, 4, 5}; int y[array_size(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

T is never deduced to be a reference or pointer type

If arg-type is a forwarding reference;- If expr is an lvalue, then both T and arg-type are deduced to be lvalue references. This is the only case in template type deduction where T becomes a reference type If expr is an rvalue then the non-forwarding reference/pointer rules apply Function Decl. Call T arg-type void fn( T && a) int b = 3;

fn(b); int& int& const int c = 3;

fn(c); const int& const int& const int& d = b;

fn(d); const int& const int& fn(83); int int&& Note b, c and d are all lvalues, but 83 is an rvalue. This distinction is maintained in the deduction of arg-type For forwarding reference arguments, the type of the argument also encodes whether the supplied value was an lvalue or an rvalue; when an lvalue is supplied, T becomes a T & (lvalue reference). When an rvalue is supplied, T remains as just T (by-value). It is this distinction that determines whether the argument is an lvalue or rvalue reference within the function and is what allows std::forward to work See also Reference Collapsing



If arg-type is neither a pointer or reference (ie, it is passed-by-value);- If expr is a reference type then ignore the reference part Then, ignore any const or volatile attributes of expr The result is T Function Decl. Call T arg-type Notes void fn( T a) int b = 3;

fn(b); int int const int c = 3;

fn(c); int int const int& d = b;

fn(d); int int const int* const e = &b;

fn(e); const int* const int* #1 const int f[] = {1, 2, 3};

fn(f); const int* const int* #2 void g(