Inline Functions and Lambda Expressions in C++
Table of Contents
- Inline Functions
- Lambda Expressions
- Lambda Captures
- Generic Lambdas
- Lambdas with STL
- std::function
- Best Practices
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 Note
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);
}
}
Best Practices
✅ 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
- Keep lambdas short - if > 5 lines, consider a named function
- Capture explicitly - avoid
[=]and[&]when possible - Watch lifetimes - captured references must outlive lambda
- Use
auto- each lambda has a unique type - Prefer templates over
std::functionwhen possible
Quick Reference
// 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