Polyglot
Languages C++ syntax
C++ § syntax

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 qualified const to 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 be constexpr; values declared constexpr are usable in constant expressions.
  • consteval (C++20) — a function that must be evaluated at compile time. Calling a consteval function 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:

SpecifierEffect
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.)
staticStatic 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.
externIndicates that the declaration refers to an entity defined elsewhere; gives external linkage.
thread_localStatic storage duration with one instance per thread.
mutablePermits 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:

AttributeEffect
[[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.