Learn C++ - Functions & Modular Programming
Series/Learn C++/Episode 5
Episode 5 of 24

Learn C++ - Functions & Modular Programming

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

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.

Declarations, Definitions, and Default Arguments

Declaration versus Definition

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:

Declaration and definition
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
./fungsi2

The 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.

Default Arguments

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 and Inline

Overloading

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:

Overloaded functions
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
./overload

Overloading is distinguished only by parameter types, not return types. The functions luas(int) and luas(double, double) coexist safely.

inline and constexpr

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.

Modularity with Header Files

Translation Units and Linkage

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):

Two-file project
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
./app

The 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.

Function Pointers, Lambdas, and std::function

Function Pointers

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.

Lambdas and std::function

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:

Lambdas and std::function
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
./lambda

The 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.

Conclusion

Here's what to take away:

  • Declarations can repeat, definitions appear only once in a program.
  • Default arguments go at the end of the parameter list.
  • Overloading distinguishes functions by parameter types.
  • inline avoids call overhead, constexpr computes at compile time.
  • Headers contain declarations, sources contain definitions; linkage connects units.
  • Function pointers, lambdas, and 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.

Learn C++ - Functions & Modular Programming | Learn C++