This episode dissects functions thoroughly: declarations versus definitions, overloading, default arguments, inline and constexpr, splitting headers and sources with linkage, as well as function pointers, lambdas, and std::function for treating functions as values.

Functions are the basic unit of modularity in C++. Episode 3 introduced simple functions; episode 5 deepens them: how declarations and definitions differ, how overloading lets one name serve many forms, and how modern C++ turns functions into first-class citizens.
By the end of this episode, you'll build a multi-file project with correct headers, understand linkage that causes many linker errors, and use lambdas for small logic that doesn't need a named function.
A declaration tells the compiler that a function exists along with its type. A definition provides the function's body. A function may be declared many times but may only be defined once in a program:
cat > fungsi2.cpp <<'EOF'
#include <iostream>
int tambah(int a, int b);
int tambah(int a, int b);
int tambah(int a, int b) {
return a + b;
}
int main() {
std::cout << tambah(3, 4) << "\n";
}
EOF
g++ -std=c++20 fungsi2.cpp -o fungsi2
./fungsi2The declaration int tambah(int a, int b); appears twice without issue, while the definition appears only once. This pattern lets headers contain declarations and sources contain definitions.
Parameters can be given a default value used when the argument isn't provided. The rule: defaulted parameters go at the end of the parameter list. The call sapa("Arman") uses the default value, and the second call overrides it with an explicit value.
Overloading allows several functions with the same name but different parameters. The compiler picks the right version based on argument types — this is the reason for the name mangling discussed in episode 2:
cat > overload.cpp <<'EOF'
#include <iostream>
int luas(int sisi) {
return sisi * sisi;
}
double luas(double panjang, double lebar) {
return panjang * lebar;
}
int main() {
std::cout << luas(5) << "\n";
std::cout << luas(2.5, 4.0) << "\n";
}
EOF
g++ -std=c++20 overload.cpp -o overload
./overloadOverloading is distinguished only by parameter types, not return types. The functions luas(int) and luas(double, double) coexist safely.
An inline function is suggested to the compiler to copy its body directly to the call site, avoiding call overhead. constexpr makes a function computed at compile time if all its arguments are constants — constexpr int hasil = pangkatDua(9) forces the computation to happen at compile time. Use constexpr for functions that can be computed earlier — saving runtime without changing the result.
Each .cpp file is a single translation unit compiled on its own. Linkage connects symbols between units: global-named functions have external linkage (usable by other units), and static gives internal linkage.
A multi-file project is split into headers (declarations) and sources (definitions):
cat > kalkulator.hpp <<'EOF'
#ifndef KALKULATOR_HPP
#define KALKULATOR_HPP
int tambah(int a, int b);
int kali(int a, int b);
#endif
EOF
cat > kalkulator.cpp <<'EOF'
#include "kalkulator.hpp"
int tambah(int a, int b) { return a + b; }
int kali(int a, int b) { return a * b; }
EOF
cat > main.cpp <<'EOF'
#include "kalkulator.hpp"
#include <iostream>
int main() {
std::cout << tambah(2, 3) << " " << kali(2, 3) << "\n";
}
EOF
g++ -std=c++20 main.cpp kalkulator.cpp -o app
./appThe command g++ -std=c++20 main.cpp kalkulator.cpp -o app compiles two source files then links them into one executable. #include "kalkulator.hpp" uses quotes because the header is in the local directory. The include guard #ifndef KALKULATOR_HPP prevents the header from being processed twice.
A function has an address, and that address can be stored in a variable. A function pointer lets a function be passed as an argument — the type int (*fn)(int, int) points to a function that takes two ints and returns an int. This is the classic callback pattern, though nowadays it's more often replaced by lambdas.
A lambda defines an anonymous function right where it's used. std::function stores any callable — a lambda, function pointer, or functor — with a uniform type:
cat > lambda.cpp <<'EOF'
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
auto kali2 = [](int x) { return x * 2; };
std::cout << kali2(5) << "\n";
std::vector<int> angka{3, 1, 4, 1, 5};
std::sort(angka.begin(), angka.end(),
[](int a, int b) { return a > b; });
std::function<int(int)> fn = kali2;
std::cout << fn(6) << "\n";
}
EOF
g++ -std=c++20 lambda.cpp -o lambda
./lambdaThe lambda [](int x) { return x * 2; } captures surrounding variables in the square brackets and accepts parameters in the parentheses. std::sort(angka.begin(), angka.end(), [](int a, int b){:bash} ...) uses a lambda as the comparator. Deeper details are covered in episode 16.
Here's what to take away:
inline avoids call overhead, constexpr computes at compile time.std::function treat functions as values.In the next episode, episode 6, we'll discuss object-oriented programming — classes and objects, constructors and destructors, access specifiers and encapsulation, inheritance and polymorphism with virtual functions, abstract classes and the interface pattern, as well as smart pointers for object ownership.