Functions and Lambdas
|
This section documents C++23 (ISO/IEC 14882:2024), as published by ISO/IEC JTC1/SC22/WG21 (wg21), verified against the freely available working draft N5046 (eel.is/c++draft) and cppreference.com. This content was generated with the assistance of AI and should be verified against the working draft and cppreference.com before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Declarations, Overloading, and Default Arguments
int add(int a, int b);
double add(double a, double b); // overload: same name, different parameter types
int add(int a, int b, int c = 0); // default argument -- callable as add(1, 2) or add(1, 2, 3)
// int add(int a, int b); // error if both existed: return type alone cannot distinguish overloads
Overload resolution picks the best viable match by implicit-conversion ranking (exact match > promotion > standard conversion > user-defined conversion); an ambiguous call is a compile error, never a silent guess.
Defaulted and Deleted Functions
class NonCopyable {
public:
NonCopyable() = default; // ask the compiler for its usual implementation
NonCopyable(const NonCopyable&) = delete; // forbid copying entirely -- a compile error, not UB
NonCopyable& operator=(const NonCopyable&) = delete;
NonCopyable(NonCopyable&&) = default; // moving is still allowed
NonCopyable& operator=(NonCopyable&&) = default;
};
void onlyIntegers(int) {}
template <typename T> void onlyIntegers(T) = delete; // = delete also blocks a whole overload set for other T
See Classes and Objects for how = default/= delete
interact with the rule of zero/three/five.
Lambdas
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
auto square = [](int x) { return x * x; }; // no captures
int factor = 3;
auto scale = [factor](int x) { return x * factor; }; // capture by value (a snapshot at creation)
auto scaleRef = [&factor](int x) { return x * factor; }; // capture by reference (sees later changes)
auto captureAll = [=]() { return factor; }; // capture everything used, by value
auto captureAllRef = [&]() { factor++; }; // capture everything used, by reference
std::vector<int> v = {5, 3, 1, 4, 2};
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // descending
for (int x : v) std::cout << x << ' ';
auto generic = [](auto a, auto b) { return a + b; }; // generic lambda (C++14): implicitly a template
std::cout << generic(1, 2) << generic(1.5, 2.5) << '\n';
auto templated = []<typename T>(std::vector<T> const& vec) { return vec.size(); }; // template lambda (C++20)
std::cout << templated(v) << '\n';
(void)square; (void)scale; (void)scaleRef; (void)captureAll; (void)captureAllRef;
}
Recursive Lambdas
A lambda cannot name itself directly, but C++23’s deducing this lets it take a self-parameter:
auto factorial = [](this auto self, int n) -> int {
return n <= 1 ? 1 : n * self(n - 1);
};
static_assert(factorial(5) == 120);
Before C++23, the usual workarounds were std::function with a captured reference to itself, or
std::function passed by reference into the lambda’s own capture list.
std::function, std::invoke, and Function Composition
#include <functional>
#include <iostream>
int addTwo(int x) { return x + 2; }
struct Multiplier {
int factor;
int operator()(int x) const { return x * factor; }
};
int main() {
std::function<int(int)> f = addTwo; // type-erased callable: functions, lambdas, functors all fit
f = [](int x) { return x * 10; };
std::cout << f(5) << '\n'; // 50
Multiplier times3{3};
std::cout << std::invoke(times3, 5) << '\n'; // 15 -- std::invoke handles callables uniformly, including
// member function pointers and pointers-to-member-data
auto compose = [](auto g, auto h) {
return [g, h](auto x) { return g(h(x)); };
};
auto addThenDouble = compose([](int x) { return x * 2; }, addTwo);
std::cout << addThenDouble(3) << '\n'; // (3+2)*2 = 10
}
Higher-order functions — functions taking or returning other functions, like compose above, or the
map/fold-style algorithms in Iterators and
Algorithms — are idiomatic in modern C++ thanks to lambdas and templates.
noexcept and [[nodiscard]]
void mayThrow();
void neverThrows() noexcept; // promises not to throw; std::terminate if it does anyway
template <typename T>
void swapValues(T& a, T& b) noexcept(noexcept(std::swap(a, b))) // conditional noexcept
{
using std::swap;
swap(a, b);
}
[[nodiscard]] int computeChecksum(int data); // ignoring the return value is now a compiler warning
noexcept functions let containers like std::vector choose to move elements on reallocation instead of
copying them (see Move Semantics and Value
Categories) — the strong exception-safety guarantee otherwise requires a fallback to copying.
See Also
-
C: Functions — no overloading, no default arguments and no lambdas — function pointers carry that weight in C.