A function has a name, a parameter list and a return type [dcl.fct]. This page covers how functions are declared, how a call picks an overload, and the two forms that are built on functions: lambda expressions and coroutines.
Declaring a function
int add(int a, int b); // declaration
int add(int a, int b) { return a + b; } // definition
auto mul(int a, int b) -> int; // trailing return type (C++11)
auto half(int a) { return a / 2; } // deduced return type (C++14)
void log(const char* format, ...); // C variadic function
void f() noexcept; // does not throw (C++11)
inline int one() { return 1; } // may be defined in several translation units
- Top-level
conston a parameter is ignored in the function’s type:void f(int)andvoid f(const int)are the same function. - An array or function parameter is adjusted to a pointer:
void f(int a[10])is reallyvoid f(int*). noexcepthas been part of a function’s type since C++17.- A function may not return an array or a function, though it can return a reference or pointer to one.
f()declares a function with no parameters. In C before C23, it does not.
Default arguments
A trailing parameter may have a default value, used when the argument is omitted [dcl.fct.default].
void open(const char* path, int mode = 0, bool create = false);
open("a"); // open("a", 0, false)
open("a", 1); // open("a", 1, false)
- Once a parameter has a default, all those after it must too.
- A later declaration in the same scope may add defaults, but may not redefine one.
- A default can name globals and earlier declarations, but not other parameters, local variables or
this. - The expression is evaluated each time the default is used.
- A virtual function uses the default of its static type.
Overloading
Several functions in one scope may share a name if their parameter types differ
[over.load]. Return type alone does not distinguish overloads. A member function can
also overload on const, volatile and ref-qualifiers.
Overload resolution
For each call, the compiler finds the best function in three steps [over.match]:
- Candidate functions. Name lookup, which for a call includes argument-dependent lookup, gives the set. Function templates produce candidates through deduction.
- Viable functions. A candidate is viable if it accepts the number of arguments given, each can be implicitly converted, and any associated constraints are satisfied [over.match.viable].
- Best viable function. One function must be better than all the others [over.match.best]. If none is, the call is ill-formed.
A function F1 is better than F2 if it is no worse for every argument, and better for at least one, or if a tie-breaker applies.
| Rank | Category | Examples |
|---|---|---|
| Exact match | Identity, lvalue transformations, qualification adjustment | int to int, array to pointer, int* to const int* |
| Promotion | Integral or floating-point promotion | char to int, float to double |
| Conversion | Any other standard conversion | int to double, double to int |
| User-defined | A conversion function or converting constructor | const char* to std::string |
| Ellipsis | Passed through ... | Lowest priority |
Each standard conversion sequence has the rank of its worst step [over.ics.scs]. Among references, a binding of an rvalue reference to an rvalue is better than a const lvalue reference binding, and the less cv-qualified binding wins [over.ics.rank].
If two functions are still tied, these tie-breakers apply, in order [over.match.best]:
- The context is initialisation by user-defined conversion, and F1’s return type converts better to the target type.
- The context is binding a reference to function, and F1’s return type matches the reference kind.
- F1 is not a function template specialisation and F2 is.
- Both are template specialisations, and F1’s is more specialised [temp.func.order].
- Neither is a template, the parameters match, and F1 is more constrained.
- F1 is a constructor of a derived class and F2 is an inherited base constructor, with the same parameters.
- F2 is a rewritten candidate, and F1 is not.
- Both are rewritten, and F2 is the reversed one.
- In class template argument deduction, F1 was not derived from an inherited constructor and F2 was.
- F1 was generated from a deduction guide and F2 was not.
- F1 is the copy deduction candidate and F2 is not.
- F1 was generated from a non-template constructor, and F2 from a constructor template.
Lambda expressions
A lambda expression (C++11) makes an unnamed function object, called a closure, in place [expr.prim.lambda].
lambda-expression:
lambda-introducer [attribute-specifier-seq] lambda-declarator compound-statement
lambda-introducer < template-parameter-list > [requires-clause]
[attribute-specifier-seq] lambda-declarator compound-statement
lambda-introducer:
[ [lambda-capture] ]
lambda-specifier: consteval | constexpr | mutable | static
(Here the outer square brackets of lambda-introducer are literal.)
auto a = [](int x) { return x * 2; }; // no captures
auto b = [total = 0](int x) mutable { return total += x; }; // init-capture (C++14)
auto c = [](auto x, auto y) { return x + y; }; // generic lambda (C++14)
auto d = []<class T>(std::vector<T> v) { return v.size(); }; // template lambda (C++20)
auto e = [](int x) static { return x; }; // static lambda (C++23)
Captures
A capture is how a lambda gets variables from the enclosing scope [expr.prim.lambda.capture]. Only variables with automatic storage duration are captured; globals and statics are used directly.
| Capture | Meaning | Since |
|---|---|---|
[] | Nothing | C++11 |
[x] | x by copy | C++11 |
[&x] | x by reference | C++11 |
[=] | Each used variable by copy | C++11 |
[&] | Each used variable by reference | C++11 |
[this] | The enclosing object, by pointer | C++11 |
[*this] | The enclosing object, by copy | C++17 |
[name = expr] | Init-capture: a new member, initialised from the expression | C++14 |
[&name = expr] | Init-capture by reference | C++14 |
[pack...] | A captured pack expansion | C++11 |
[...pack = expr] | A pack expansion in an init-capture | C++20 |
- A default capture may be combined with explicit ones:
[=, &total]. - Since C++20 implicit capture of
thisby[=]is deprecated, and[=, this]is the explicit form. - A reference capture must not outlive the variable it refers to.
- The generated function call operator is
constunless the lambda ismutable, so by-copy captures cannot change without it.
The closure type
Each lambda expression has a unique, unnamed class type [expr.prim.lambda.closure], with
an operator(), plus a member per capture. A lambda with no captures can convert
to a pointer to function. Since C++20 such a lambda is also default-constructible and assignable.
Since C++17 a lambda’s call operator is implicitly constexpr if it can
be. Since C++23 static removes the implicit object parameter. A
lambda can recurse by naming itself in an explicit object parameter:
[](this auto self, int n) -> int { return n ? n * self(n - 1) : 1; }.
Coroutines
A coroutine (C++20) is a function that can suspend itself and be resumed later
[dcl.fct.def.coroutine]. A function becomes a coroutine if its body contains
co_await, co_yield or co_return.
| Keyword | Effect | Section |
|---|---|---|
co_await expr | Suspends until an awaitable result is ready | [expr.await] |
co_yield expr | Suspends and produces a value | [expr.yield] |
co_return | Finishes the coroutine | [stmt.return.coroutine] |
Coroutines cannot be main, constructors, destructors, constexpr or
consteval functions, or functions with a deduced return type, or variadic with a C-style
ellipsis.
The promise type
The return type’s std::coroutine_traits names a promise type, whose member
functions control the coroutine. The compiler rewrites the body as:
{
promise-type promise promise-constructor-arguments;
try {
co_await promise.initial_suspend();
function-body
} catch (...) {
if (!initial-await-resume-called)
throw;
promise.unhandled_exception();
}
final-suspend:
co_await promise.final_suspend();
}
| Promise member | Purpose |
|---|---|
get_return_object() | Makes the object returned to the caller |
initial_suspend() | An awaitable: whether to suspend before the body starts |
final_suspend() | An awaitable: whether to suspend at the end. It must not be potentially throwing |
return_value(v) or return_void() | Handles co_return. A promise may declare only one of them |
yield_value(v) | Handles co_yield |
unhandled_exception() | Called when the body throws |
await_transform(e) | Optional: changes every co_await operand |
get_return_object_on_allocation_failure() | Optional: used when allocating the frame fails |
The local state lives in a coroutine frame, which the compiler allocates, usually on the
heap, and may optimise away. Flowing off the end of a coroutine with no
return_void is undefined behaviour, as is resuming or destroying one that is not
suspended.
The awaiter protocol
co_await works on any type that gives an awaiter with three member
functions [expr.await].
| Member | Role |
|---|---|
await_ready() | If true, the coroutine does not suspend |
await_suspend(h) | Runs after suspending, with a std::coroutine_handle. Can return void, a bool, or another handle to resume |
await_resume() | Its result is the value of the co_await expression |
#include <generator>
std::generator<int> iota(int start, int stop) // C++23
{
for (int i = start; i < stop; ++i)
co_yield i;
}
for (int n : iota(0, 3))
std::cout << n; // 012
std::generator (C++23) is a coroutine type from the standard library