C++ Cheatsheet

Functions

Use this C++ reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Declaration and Definition

// Declaration (prototype) — in header
int add(int a, int b);

// Definition — in .cpp
int add(int a, int b) {
    return a + b;
}

// Combined declaration + definition
double square(double x) { return x * x; }

// Void function
void greet(const std::string& name) {
    std::cout << "Hello, " << name << "\n";
}

Parameters

// Pass by value (copy)
void byVal(int x) { x = 99; }       // caller's variable unchanged

// Pass by reference (alias to caller's variable)
void byRef(int& x) { x = 99; }      // caller's variable IS changed

// Pass by const reference (read-only, no copy)
void byConstRef(const std::string& s) { /* s unmodifiable */ }

// Pass by pointer
void byPtr(int* p) { *p = 99; }     // caller: byPtr(&x);
void byConstPtr(const int* p) { /* *p unmodifiable */ }

// Move semantics (avoid unnecessary copy of expensive objects)
void takesVec(std::vector<int> v) { /* owns the data */ }
// caller: takesVec(std::move(myVec));

Rule of thumb: pass small/trivial types by value; pass large objects by const&; pass when ownership is transferred by value with std::move.

Default Arguments

void log(const std::string& msg, int level = 1, bool newline = true);

log("hello");          // level=1, newline=true
log("hello", 2);       // level=2, newline=true
log("hello", 3, false);

// Defaults must trail non-defaulted params.
// Define defaults only in the declaration (not definition in separate TU).

Return Values

int sum(int a, int b) { return a + b; }

// Return by reference (must outlive the function!)
int& getElement(std::vector<int>& v, int i) { return v[i]; }
getElement(v, 0) = 99;   // assign through reference

// Return multiple values
std::pair<int, int> divmod(int a, int b) {
    return {a / b, a % b};
}
auto [q, r] = divmod(10, 3);   // C++17

// std::tuple for more than two
std::tuple<int, double, std::string> multi() {
    return {1, 3.14, "hi"};
}
auto [a, b, c] = multi();

// Named Return Value Optimization (NRVO) — compiler elides copy:
std::vector<int> makeVec() {
    std::vector<int> v = {1, 2, 3};
    return v;   // usually no copy (NRVO)
}

Function Overloading

int    max(int a, int b)       { return a > b ? a : b; }
double max(double a, double b) { return a > b ? a : b; }
int    max(int a, int b, int c){ return max(max(a, b), c); }

// Overloads must differ by parameter types or count (NOT return type alone)
// void f(int);  and  int f(int);  — NOT valid overloads

inline, constexpr, [[nodiscard]]

// inline: hint to expand at call site; also suppresses ODR error for
// definitions in headers (modern headers with definitions use inline)
inline int inc(int x) { return x + 1; }

// constexpr: evaluated at compile time when args are compile-time constants
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int f6 = factorial(6);   // 720, computed at compile time
int runtime = factorial(n);        // also works at runtime

// consteval (C++20): MUST be evaluated at compile time
consteval int sq(int x) { return x * x; }

// [[nodiscard]]: warn if caller ignores the return value
[[nodiscard]] int readFile(const std::string& path);
// [[nodiscard("reason")]] (C++20)

Variadic Functions

// C-style variadic (avoid; use variadic templates instead)
#include <cstdarg>
int sumAll(int count, ...) {
    va_list ap;
    va_start(ap, count);
    int total = 0;
    for (int i = 0; i < count; ++i)
        total += va_arg(ap, int);
    va_end(ap);
    return total;
}

// Variadic templates (C++11, type-safe)
template<typename... Args>
void print(Args... args) {
    (std::cout << ... << args);  // fold expression (C++17)
}
print(1, " ", 2.5, " ", "hello");

// Fold expressions (C++17)
template<typename... Ts>
auto sum(Ts... vals) { return (... + vals); }  // left fold: ((a + b) + c)

Function Pointers

// Declare: return_type (*name)(param_types)
int (*fp)(int, int) = add;
int result = fp(2, 3);    // call through pointer

// Simplify with alias
using BinaryOp = int(*)(int, int);
BinaryOp op = add;

// Passing function pointers
void apply(int x, int y, int (*op)(int, int)) {
    std::cout << op(x, y) << "\n";
}
apply(3, 4, add);

// Array of function pointers
int (*ops[])(int, int) = {add, sub, mul};
ops[0](1, 2);

std::function (Type-Erased Callable)

#include <functional>

std::function<int(int, int)> f = add;       // function pointer
f = [](int a, int b){ return a + b; };      // lambda
f = std::bind(add, std::placeholders::_1, 10); // partial application

// Useful for storing heterogeneous callables; slight runtime overhead vs. templates

std::bind and Partial Application

#include <functional>

int add(int a, int b) { return a + b; }

auto add5 = std::bind(add, std::placeholders::_1, 5);
add5(3);   // 8

// Prefer lambdas (clearer)
auto add5L = [](int a){ return add(a, 5); };

Recursion

// Direct recursion
int fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);  // exponential without memoization
}

// Tail-recursive style (compiler may optimize, not guaranteed in C++)
int factorial(int n, int acc = 1) {
    if (n <= 1) return acc;
    return factorial(n - 1, n * acc);
}

// Memoization with static or std::unordered_map
#include <unordered_map>
int fibMemo(int n) {
    static std::unordered_map<int, int> memo;
    if (n <= 1) return n;
    if (memo.count(n)) return memo[n];
    return memo[n] = fibMemo(n-1) + fibMemo(n-2);
}

Argument-Dependent Lookup (ADL / Koenig Lookup)

namespace Lib {
    struct Widget {};
    void process(Widget w);  // found via ADL
}

Lib::Widget w;
process(w);   // OK — compiler searches Lib:: because w is of type Lib::Widget

Special Qualifiers

// noexcept: promise not to throw (enables optimizations)
int safe() noexcept { return 42; }
void maybe() noexcept(false);          // same as default
void alwaysNoexcept() noexcept(true);

// noexcept conditional on expression
template<typename T>
void f(T x) noexcept(noexcept(T{x})); // noexcept if copy-construct is noexcept

// [[maybe_unused]] — suppress unused-parameter warning
void callback(int value, [[maybe_unused]] int unused) { use(value); }

// Deleted functions
void noAlloc() = delete;              // call is compile error

Common Pitfalls

  • Returning a reference to a local variable — undefined behavior (local is destroyed on return).
  • Default argument not in declaration — put defaults only in the declaration visible to callers.
  • Overload resolution ambiguity — calling f(0) when both f(int) and f(double*) exist.
  • Passing array by value decays to pointer — use std::span or template on size.
// Wrong:
void f(int arr[10]);   // arr is actually int*

// Right:
void f(std::span<int> arr);         // C++20
template<std::size_t N>
void f(int (&arr)[N]);              // reference to array of known size