Lesson 88 min read

Functions

C++ functions are Swiss Army knives β€” same name, different blades, and they can even take default settings.

More Than Just "Call and Return"

In many languages, a function name is unique β€” one name, one behavior. C++ breaks that rule in a powerful way. You can have multiple functions with the same name that do different things based on what you pass them. This is called function overloading, and it's one of the features that makes C++ feel expressive.

But that's just the start. C++ also lets you set default argument values, choose whether to copy or reference data, and even create tiny anonymous functions on the fly. Let's explore each of these tools.

Function Overloading β€” Same Name, Different Blades

Imagine a print() function. Sometimes you want to print an integer. Sometimes a string. Sometimes a vector. In C, you'd need printInt(), printStr(), printVec(). In C++, they can all be called print() β€” the compiler figures out which one to call based on the number and types of arguments.

Function Overloading in Action

#include <iostream>
#include <string>
#include <vector>
using namespace std;
void print(int x) {
cout << "Integer: " << x << endl;
}
void print(const string& s) {
cout << "String: " << s << endl;
}
void print(const vector<int>& v) {
cout << "Vector: [";
for (size_t i = 0; i < v.size(); i++) {
if (i > 0) cout << ", ";
cout << v[i];
}
cout << "]" << endl;
}
int main() {
print(42);
print(string("hello"));
print(vector<int>{1, 2, 3, 4, 5});
return 0;
}
Output
Integer: 42
String: hello
Vector: [1, 2, 3, 4, 5]

Default Arguments β€” Pre-Set the Knobs

Sometimes a function has parameters that usually take the same value. Instead of making callers type it every time, you can give parameters default values. Defaults must start from the rightmost parameter and go left β€” you can't skip parameters in the middle.

Default Arguments

#include <iostream>
#include <string>
using namespace std;
void greet(const string& name, const string& greeting = "Hello", int times = 1) {
for (int i = 0; i < times; i++) {
cout << greeting << ", " << name << "!" << endl;
}
}
int main() {
greet("Alice"); // Uses both defaults
greet("Bob", "Hey"); // Uses default for times
greet("Charlie", "Yo", 3); // No defaults used
return 0;
}
Output
Hello, Alice!
Hey, Bob!
Yo, Charlie!
Yo, Charlie!
Yo, Charlie!

Pass by Value vs. Pass by Reference

This is where C++ gives you control that most languages hide. When you pass an argument to a function, you choose:

  • By value β€” the function gets a copy. Changes inside don't affect the original.
  • By reference (&) β€” the function gets the original. Changes inside modify the caller's data.
  • By const reference (const &) β€” the function gets the original but promises not to modify it. Best of both worlds for read-only access.

Think of it like lending a book. By value = you photocopy the book and give the copy. By reference = you hand over the actual book. By const reference = you hand over the book but say "just read it, don't write in it."

Pass by Reference β€” The Classic Swap

#include <iostream>
using namespace std;
// By value β€” does NOT work
void badSwap(int a, int b) {
int temp = a;
a = b;
b = temp;
// Only swaps the local copies!
}
// By reference β€” works!
void goodSwap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 10, y = 20;
badSwap(x, y);
cout << "After badSwap: x=" << x << " y=" << y << endl;
goodSwap(x, y);
cout << "After goodSwap: x=" << x << " y=" << y << endl;
return 0;
}
Output
After badSwap:  x=10 y=20
After goodSwap: x=20 y=10

const Reference β€” Fast & Safe for Large Objects

#include <iostream>
#include <vector>
using namespace std;
// BAD: copies the entire vector (could be millions of elements!)
int sumByValue(vector<int> nums) {
int total = 0;
for (int n : nums) total += n;
return total;
}
// GOOD: no copy, and const prevents accidental modification
int sumByConstRef(const vector<int>& nums) {
int total = 0;
for (int n : nums) total += n;
return total;
}
int main() {
vector<int> data = {10, 20, 30, 40, 50};
cout << "Sum (by value): " << sumByValue(data) << endl;
cout << "Sum (by const ref): " << sumByConstRef(data) << endl;
// Both return 150, but const ref avoids copying
return 0;
}
Output
Sum (by value):     150
Sum (by const ref): 150
Note: Pass large objects by const reference (const std::string&, const std::vector&) β€” passing by value copies the entire object! Only pass by value for small, cheap-to-copy types like int, char, bool, and double.

Inline Functions & auto Return Type

inline is a hint to the compiler: "This function is tiny β€” please paste its body directly at the call site instead of making a real function call." Modern compilers usually decide this on their own, so inline is mostly used for header-file functions to avoid linker errors.

C++14 introduced auto return types β€” the compiler deduces the return type from the return statement. Handy for templates and simple functions.

Lambdas β€” Functions on the Fly

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// A simple lambda
auto add = [](int a, int b) { return a + b; };
cout << "3 + 4 = " << add(3, 4) << endl;
// Lambda with capture β€” grabs a variable from the outside
int factor = 3;
auto multiply = [factor](int x) { return x * factor; };
cout << "5 * 3 = " << multiply(5) << endl;
// Lambdas shine with STL algorithms
vector<int> nums = {5, 2, 8, 1, 9, 3};
// Sort descending using a lambda comparator
sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b;
});
cout << "Sorted: ";
for (int n : nums) cout << n << " ";
cout << endl;
// Count elements greater than 4
int count = count_if(nums.begin(), nums.end(), [](int n) {
return n > 4;
});
cout << "Elements > 4: " << count << endl;
return 0;
}
Output
3 + 4 = 7
5 * 3 = 15
Sorted: 9 8 5 3 2 1
Elements > 4: 3

Lambda Syntax Cheat Sheet

The general form is: [capture](parameters) -> return_type { body }

  • [] β€” capture nothing from the outside scope
  • [x] β€” capture x by value (copy)
  • [&x] β€” capture x by reference
  • [=] β€” capture everything by value
  • [&] β€” capture everything by reference

Lambdas are especially useful with STL algorithms like sort, for_each, find_if, and count_if.

Challenge

Quick check

What determines which overloaded function is called?
← StringsClasses & Objects β†’