Syntax
The syntax of C++ is defined by ISO/IEC 14882 as an evolution of C’s syntax with substantial additions for declarations, type specifiers, class definitions, templates, and the modern facilities of each subsequent revision. The lexical surface — the keywords, identifiers, literals, and punctuators — has grown over each standard, and contemporary C++ recognises around ninety keywords. The grammar is correspondingly large; this page covers the surface a working programmer encounters routinely, with cross-references to the dedicated pages for templates, classes, and the other major sub-grammars.
A complete program
A conforming hosted program defines main and includes the headers that declare the library facilities it uses:
#include <iostream>
#include <string>
#include <vector>
int main(int argc, char *argv[]) {
std::vector<std::string> args(argv + 1, argv + argc);
if (args.empty()) {
std::cerr << "usage: " << argv[0] << " <name>...\n";
return 1;
}
for (const auto &name : args) {
std::cout << "Hello, " << name << ".\n";
}
return 0;
}
The file is a translation unit. After preprocessing — #include directives are replaced by the contents of the named files, macros are expanded, conditionals are resolved — the compiler sees a sequence of tokens against which it performs lexical analysis, parsing, name lookup, template instantiation, and type checking. The resulting code is emitted as object code and linked with any libraries the program references.
C++20 introduced modules as an alternative to the inclusion model; a module-based version of the same program would import std; rather than #include <iostream>. The classic inclusion model remains dominant; modules are treated separately in Modules and translation units.
Source character set
The standard distinguishes the source character set from the execution character set. The basic source set consists of the upper- and lower-case letters of the English alphabet, the decimal digits, and a set of graphic characters: ! " # % & ' ( ) * + , - . / : ; < = > ? [ \ ] ^ _ { | } ~. Whitespace consists of the space, the horizontal tab, the vertical tab, the form feed, and the new-line. C++23 made UTF-8 the default source-file encoding; before C++23, the encoding was implementation-defined and most implementations accepted UTF-8 already.
Comments
C++ supports two comment forms:
/* a block comment, possibly spanning multiple lines */
// a line comment, terminated by the end of the line
Block comments do not nest. Both forms have been available since C++98; the line form predominates in contemporary code for short remarks.
Identifiers and keywords
An identifier consists of a non-digit character followed by any number of digit and non-digit characters; the rules are essentially those of C. The keywords are reserved and may not be used as identifiers. The C++23 keyword list contains around ninety entries:
alignas consteval goto return
alignof constexpr if short
asm constinit inline signed
auto const_cast int sizeof
bool continue long static
break co_await mutable static_assert
case co_return namespace static_cast
catch co_yield new struct
char decltype noexcept switch
char8_t default nullptr template
char16_t delete operator this
char32_t do private thread_local
class double protected throw
co_await dynamic_cast public true
compl else reflexpr try
concept enum register typedef
const explicit reinterpret_cast typeid
consteval export requires typename
constexpr extern union using
constinit false unsigned virtual
const_cast final void volatile
wchar_t while
Several keywords are contextual — they have keyword status only in particular grammatical positions. final and override, for example, are contextual keywords used in class definitions; they may appear as ordinary identifiers elsewhere. The contextual nature is a concession to backward compatibility.
Identifiers beginning with an underscore followed by an upper-case letter, or by a second underscore, and identifiers containing a double underscore anywhere, are reserved for the implementation. They may appear in standard headers but not in user code.
Declarations and definitions
A declaration introduces a name and gives it a type; a definition additionally allocates storage (for objects), supplies a body (for functions), or fully describes a structure (for classes):
extern int global_counter; // declaration; storage defined elsewhere
int local_counter = 0; // definition; reserves storage at namespace scope
int square(int); // function declaration (a *prototype*)
int square(int x) { return x * x; } // function definition
class Widget; // forward declaration; a class type, no members yet
class Widget { /* … */ }; // definition; supplies the members
Declarations consist of declaration specifiers — type specifiers (int, class Widget), type qualifiers (const, volatile), storage-class specifiers (static, extern, thread_local), and function specifiers (inline, virtual, explicit) — followed by one or more declarators. The declarator names the entity and combines the base type with pointer, array, reference, function, or template modifiers.
C++ adds references to C’s declarator forms:
int x = 0;
int &r = x; // r is a reference to x; r and x designate the same object
int *p = &x; // p is a pointer to int
int &&rr = 42; // rr is an rvalue reference (C++11)
References are distinct from pointers; they are bound at initialisation and cannot be rebound. The full treatment is in References and pointers.
Statements
The statement grammar resembles C’s, with several additions:
expression; // expression statement
{ /* statements */ } // compound statement (a block)
if (cond) statement // selection
if (init; cond) statement // selection with initialiser (C++17)
if constexpr (cond) statement // compile-time selection (C++17)
switch (expr) { /* cases */ }
while (cond) statement // iteration
do statement while (cond);
for (init; cond; step) statement
for (decl : range) statement // range-based for (C++11)
goto label; // unconditional transfer
continue;
break;
return expr;
throw expr; // exception
try { … } catch (…) { … } // exception handling
co_await expr; // coroutine suspension (C++20)
co_yield expr;
co_return expr;
The if constexpr, range-based for, exception, and coroutine constructions are C++ additions; the rest are inherited from C. Each statement is terminated by a semicolon or, for compound statements, by a closing brace.
Type qualifiers
C++ inherits C’s type qualifiers — const, volatile, restrict (the last as an extension only; it is not standard C++) — and adds compile-time evaluation qualifiers:
const— values that may not be modified through the qualified lvalue. Conventional throughout C++; member functions may also be qualifiedconstto indicate they do not modify the object.volatile— accesses must occur in source order; the compiler may not eliminate or reorder them.constexpr(C++11; generalised in C++14, C++17, C++20) — the qualified entity may be evaluated at compile time. Functions and constructors may beconstexpr; values declaredconstexprare usable in constant expressions.consteval(C++20) — a function that must be evaluated at compile time. Calling aconstevalfunction with non-constant arguments is a compile-time error.constinit(C++20) — an object that must be statically initialised, but is otherwise mutable. Used to avoid the static-initialisation-order fiasco.
constexpr int square(int x) { return x * x; }
constexpr int sixteen = square(4); // computed at compile time
consteval int cube(int x) { return x * x * x; }
constexpr int twenty_seven = cube(3); // also compile-time
constinit int log_level = 0; // statically initialised; mutable thereafter
Storage-class specifiers
The storage-class specifiers are essentially C’s plus thread_local:
| Specifier | Effect |
|---|---|
auto | (No storage-class effect since C++11; reused for type deduction.) |
register | (Removed as a storage-class specifier in C++17; reserved for future use.) |
static | Static storage duration. At namespace scope, restricts linkage to the translation unit. At block scope, makes the object persist across function calls. At class scope, declares a member that is shared across instances. |
extern | Indicates that the declaration refers to an entity defined elsewhere; gives external linkage. |
thread_local | Static storage duration with one instance per thread. |
mutable | Permits modification of a class member through a const member function. |
The interactions among storage class, scope, and linkage are the subject of Scope and namespaces.
Linkage specifications
C++ admits a language linkage specifier on declarations, principally to declare functions with C linkage:
extern "C" {
void c_function(int);
int c_constant;
}
extern "C" int another_c_function();
The mechanism is the conventional way to call C functions from C++ and to expose C++ functions to C callers. Without the specifier, C++ functions undergo name mangling — the linker symbol incorporates the function’s full signature — and are not callable from C.
Attributes
C++11 introduced an attribute syntax [[name]] for portable, standards-defined annotations. The principal attributes:
| Attribute | Effect |
|---|---|
[[nodiscard]] | A diagnostic is issued if the return value of an annotated function is discarded. |
[[maybe_unused]] | Suppresses unused-variable diagnostics. |
[[deprecated]] | A diagnostic is issued when the entity is used. May carry a string argument. |
[[noreturn]] | The function does not return. The optimiser need not generate a return path. |
[[fallthrough]] | Marks an intentional fallthrough between switch cases. |
[[likely]] / [[unlikely]] | (C++20) Branch prediction hints. |
[[no_unique_address]] | (C++20) The annotated member need not have a unique address; permits empty-member optimisation. |
[[assume(expr)]] | (C++23) The optimiser may assume expr is true. |
Attributes are placed before the entity they annotate (for declarations) or after the function header (for return-value annotations on functions):
[[nodiscard]] int compute_index(int n);
void deprecated_helper() [[deprecated("use new_helper")]];
A note on undefined behaviour
C++ inherits C’s category of undefined behaviour and applies it considerably more aggressively. Signed-integer overflow, dereferencing null, accessing an object outside its lifetime, invoking a function through a pointer of incompatible type, violating the strict aliasing rule, and dozens of other operations have undefined behaviour. The standard imposes no requirements on a program that exhibits undefined behaviour, and the compiler is at liberty to assume undefined behaviour does not occur — leading to optimisations that can transform such programs in surprising ways.
The discipline of writing correct C++ is largely the discipline of avoiding undefined behaviour. The language provides several mechanisms — constexpr, the [[noreturn]] attribute, sanitisers in modern toolchains — that catch a substantial fraction of the cases at compile time or at runtime. None replaces a careful reading of the standard’s specification of which constructs are permitted.