All Courses
Functions

Inline Functions and Lambda Expressions in C++


Inline Functions

What is Inline?

The inline keyword suggests the compiler replace function calls with the function body, eliminating call overhead.

inline int square(int x) {
    return x * x;
}

// Compiler may replace:
int y = square(5);
// With:
int y = 5 * 5;

When to Use Inline

  • Small functions (1-3 lines)
  • Frequently called in performance-critical code
  • Getters/setters in classes

Inline Behavior

inline int add(int a, int b) { return a + b; }  // Hint to compiler

// Modern compilers decide themselves - inline is now mostly about linkage
// Defined in header = implicitly inline-able

Class Member Functions

Functions defined inside a class are implicitly inline:

class Point {
public:
    int getX() const { return x; }  // Implicitly inline
    int getY() const;               // Not inline unless defined with 'inline'
private:
    int x, y;
};

inline int Point::getY() const { return y; }  // Explicit inline

Modern C++: constexpr

For compile-time computation, prefer constexpr:

constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

int arr[factorial(5)];  // Array of 120 elements - computed at compile time!

Lambda Expressions

What are Lambdas?

Anonymous functions defined inline. Introduced in C++11.

Basic Syntax

[captures](parameters) -> return_type { body }

// Simplest lambda
[]() { cout << "Hello!" << endl; }

// With parameters
[](int x, int y) { return x + y; }

// With explicit return type
[](double x) -> int { return static_cast<int>(x); }

Calling Lambdas

// Store in variable
auto greet = []() { cout << "Hello!" << endl; };
greet();  // Prints: Hello!

// Call immediately
[]() { cout << "Immediate!" << endl; }();

// Pass to function
sort(v.begin(), v.end(), [](int a, int b) { return a > b; });

Return Type Deduction

// Usually auto-deduced
auto add = [](int a, int b) { return a + b; };  // Returns int

// Explicit when needed
auto divide = [](int a, int b) -> double {
    return static_cast<double>(a) / b;
};

Lambda Captures

Capture Clause []

Captures variables from the enclosing scope.

Capture Modes

Syntax Meaning
[] No captures
[x] Capture x by value (copy)
[&x] Capture x by reference
[=] Capture all by value
[&] Capture all by reference
[=, &x] All by value, x by reference
[&, x] All by reference, x by value
[this] Capture this pointer
[*this] Capture this object by value (C++17)

Examples

int x = 10;
int y = 20;

// Capture x by value
auto f1 = [x]() { return x * 2; };  // x is copied

// Capture y by reference
auto f2 = [&y]() { y++; };  // Modifies original y

// Capture all by value
auto f3 = [=]() { return x + y; };

// Capture all by reference
auto f4 = [&]() { x++; y++; };

// Mixed
auto f5 = [=, &y]() {
    y = x * 2;  // x is copy, y is reference
};

Mutable Lambdas

By default, captured-by-value variables are const. Use mutable to modify copies:

int count = 0;
auto counter = [count]() mutable {
    return ++count;  // Modifies the copy
};

cout << counter() << endl;  // 1
cout << counter() << endl;  // 2
cout << count << endl;      // 0 (original unchanged)

Init Captures (C++14)

Create new variables in the capture:

auto ptr = make_unique<int>(42);

// Move into lambda
auto lambda = [p = move(ptr)]() {
    return *p;
};

// Create new variable
auto lambda2 = [value = x + y]() {
    return value * 2;
};

Generic Lambdas

Auto Parameters (C++14)

auto add = [](auto a, auto b) { return a + b; };

cout << add(1, 2) << endl;       // int
cout << add(1.5, 2.5) << endl;   // double
cout << add(string("a"), string("b")) << endl;  // string

Template Lambdas (C++20)

auto print = []<typename T>(const vector<T>& v) {
    for (const auto& elem : v) {
        cout << elem << " ";
    }
    cout << endl;
};

Lambdas with STL

Sorting

vector<int> nums = {3, 1, 4, 1, 5, 9};

// Ascending (default)
sort(nums.begin(), nums.end());

// Descending
sort(nums.begin(), nums.end(), [](int a, int b) {
    return a > b;
});

// Custom objects
vector<Person> people = {...};
sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
    return a.age < b.age;
});

Finding

vector<int> nums = {1, 2, 3, 4, 5, 6};

// Find first even
auto it = find_if(nums.begin(), nums.end(), [](int n) {
    return n % 2 == 0;
});

// Count greater than 3
int count = count_if(nums.begin(), nums.end(), [](int n) {
    return n > 3;
});

Transforming

vector<int> nums = {1, 2, 3, 4, 5};
vector<int> squares;

transform(nums.begin(), nums.end(), back_inserter(squares), [](int n) {
    return n * n;
});
// squares = {1, 4, 9, 16, 25}

Filtering

vector<int> nums = {1, 2, 3, 4, 5, 6};

// Remove odd numbers (erase-remove idiom)
nums.erase(
    remove_if(nums.begin(), nums.end(), [](int n) {
        return n % 2 != 0;
    }),
    nums.end()
);
// nums = {2, 4, 6}

For Each

vector<int> nums = {1, 2, 3, 4, 5};

for_each(nums.begin(), nums.end(), [](int& n) {
    n *= 2;
});
// nums = {2, 4, 6, 8, 10}

std::function

Type-Erased Function Wrapper

std::function can hold any callable: function, lambda, functor, member function pointer.

#include <functional>

// Declare function type
function<int(int, int)> operation;

// Assign lambda
operation = [](int a, int b) { return a + b; };
cout << operation(2, 3) << endl;  // 5

// Assign different lambda
operation = [](int a, int b) { return a * b; };
cout << operation(2, 3) << endl;  // 6

Storing Lambdas

// Store callbacks
vector<function<void()>> callbacks;

callbacks.push_back([]() { cout << "First!" << endl; });
callbacks.push_back([]() { cout << "Second!" << endl; });

for (auto& cb : callbacks) {
    cb();
}

Function as Parameter

void applyOperation(vector<int>& v, function<int(int)> op) {
    for (int& n : v) {
        n = op(n);
    }
}

vector<int> nums = {1, 2, 3};
applyOperation(nums, [](int n) { return n * 2; });
// nums = {2, 4, 6}

Performance Guide

std::function has overhead due to type erasure. For templates, prefer:

// More efficient - no type erasure
template<typename F>
void apply(vector<int>& v, F func) {
    for (int& n : v) {
        n = func(n);
    }
}

Recommended Conventions

✅ Do

// Use lambdas for short, one-off operations
sort(v.begin(), v.end(), [](int a, int b) { return a > b; });

// Capture by reference for large objects
auto process = [&bigData]() { /* use bigData */ };

// Use auto for lambda type
auto lambda = [](int x) { return x * 2; };

// Use init captures for moves
auto lambda = [ptr = move(uniquePtr)]() { /* use ptr */ };

❌ Don't

// Avoid capturing by reference if lambda outlives the variable
auto createLambda() {
    int x = 10;
    return [&x]() { return x; };  // DANGER: x destroyed!
}

// Avoid complex lambdas - use named functions instead
auto complicated = [](/* many params */) {
    // 50 lines of code...
};  // Bad - extract to named function

// Avoid unnecessary std::function overhead
std::function<int(int)> f = [](int x) { return x; };  // Overhead
auto f = [](int x) { return x; };  // Better

Guidelines

  1. Keep lambdas short - if > 5 lines, consider a named function
  2. Capture explicitly - avoid [=] and [&] when possible
  3. Watch lifetimes - captured references must outlive lambda
  4. Use auto - each lambda has a unique type
  5. Prefer templates over std::function when possible

Cheat Sheet

// Inline function
inline int square(int x) { return x * x; }

// Lambda syntax
[captures](params) -> return_type { body }

// Common patterns
[]() { }                    // No captures, no params
[](int x) { return x*2; }   // Parameter, auto return
[x]() { return x; }         // Capture by value
[&x]() { x++; }             // Capture by reference
[=]() { }                   // All by value
[&]() { }                   // All by reference
[x]() mutable { x++; }      // Modify copy

// Store in variable
auto f = [](int x) { return x * 2; };

// Pass to algorithm
sort(v.begin(), v.end(), [](int a, int b) { return a > b; });

// std::function
function<int(int)> f = [](int x) { return x * 2; };

Compile & Run

g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples