This episode covers templates as the heart of generic programming: function templates and class templates, template specialization and variadic templates, concepts and type traits, as well as std::optional and std::variant for optional and type-varying values.

One of C++'s greatest strengths is the template: a way to write code that works for various data types without duplicating code and without sacrificing performance. This is what distinguishes C++ generic programming from generics in other languages — templates are used as blueprints, and the real code is instantiated at compile time.
Episode 10 dissects templates from simple functions and classes, to specialization, variadic templates, and modern concepts like concepts and type traits. You'll also get to know std::optional and std::variant, which make your APIs more expressive.
A function template lets one function work for any type. The type is determined at compile time from the given arguments:
cat > fntmpl.cpp <<'EOF'
#include <iostream>
template <typename T>
T maksimum(T a, T b) {
return (a > b) ? a : b;
}
int main() {
std::cout << maksimum(3, 7) << "\n";
std::cout << maksimum(2.5, 1.8) << "\n";
}
EOF
g++ -std=c++20 fntmpl.cpp -o fntmpl
./fntmpltemplate <typename T> T maksimum(T a, T b) defines a generic function. When called with int, the compiler instantiates the int version; when called with double, the double version — with no runtime overhead.
The compiler deduces T from the arguments, so maksimum(3, 7) infers T = int. If both arguments have different types, you can force it with maksimum<double>(3, 7). Deduction works well as long as both arguments have the same type; otherwise, you need two template parameters or an explicit conversion.
A class template is a class parameterized by type. std::vector<int> is actually the class template vector<T> with T = int. Writing your own class template is useful for data structures that must work for many types:
cat > classtmpl.cpp <<'EOF'
#include <iostream>
#include <string>
template <typename T>
class Kotak {
public:
Kotak(T nilai) : nilai_(nilai) {}
T nilai() const { return nilai_; }
private:
T nilai_;
};
int main() {
Kotak<int> k1(42);
Kotak<std::string> k2("halo");
std::cout << k1.nilai() << " " << k2.nilai() << "\n";
}
EOF
g++ -std=c++20 classtmpl.cpp -o classtmpl
./classtmplKotak<int> k1(42) instantiates the class template with T = int, and Kotak<std::string> k2("halo") with T = std::string. The two instantiations are two different classes, each generated at compile time.
Template specialization handles special cases for a specific type — you can specialize a whole template or just part of it. For example: template <> std::string deskripsi<bool>(bool) is a full specialization for bool, used only when T = bool, while other types use the generic version. This is useful when the general behavior doesn't fit a particular type.
A variadic template accepts an indefinite number of arguments and is the basis for type-safe functions like std::printf. The pattern template <typename T, typename... Rest> separates the first argument from the rest, then calls itself again with sisa... until no arguments remain. Because code is instantiated for every argument combination, its processing runs recursively.
Since C++20, concepts give names to type requirements. With concepts, template errors are detected with clear messages instead of pages of confusing errors:
cat > concept.cpp <<'EOF'
#include <iostream>
#include <concepts>
template <std::integral T>
T ganda(T nilai) {
return nilai * 2;
}
int main() {
std::cout << ganda(21) << "\n";
}
EOF
g++ -std=c++20 concept.cpp -o concept
./concepttemplate <std::integral T> restricts T to integral types like int or long. Calling ganda(3.5) produces a clear compile error because double isn't integral.
Type traits in <type_traits> let you inspect and modify type properties at compile time — std::is_integral_v<T>, std::remove_reference_t<T>, and similar give you type introspection. With static_assert(std::is_integral_v<int>{:bash} ...), you validate assumptions at compile time; if it fails, the program won't build.
std::optional<T> wraps a T value that may not exist and replaces the classic code that used sentinel values like -1 or null pointers. std::nullopt marks the absence of a value, and *hasil retrieves the existing value. Function types now express the possibility of failure explicitly.
std::variant<T1, T2, ...> stores one value out of several possible types. Only one alternative is active at a time, and access is checked to be safe:
cat > variant.cpp <<'EOF'
#include <iostream>
#include <variant>
int main() {
std::variant<int, std::string> v;
v = 42;
std::cout << std::get<int>(v) << "\n";
v = "teks";
if (auto* p = std::get_if<int>(&v)) {
std::cout << "int: " << *p << "\n";
} else {
std::cout << "bukan int\n";
}
}
EOF
g++ -std=c++20 variant.cpp -o variant
./variantstd::get_if<int>(&v) returns a pointer to the value if v is currently storing an int, or nullptr otherwise. variant and optional let your code state possibilities more honestly than plain types.
Here's what to take away:
Kotak<int> are the basis of generic programming.std::optional and std::variant safely express optional and multi-type values.In the next episode, episode 11, we'll discuss error handling and debugging — exceptions with throw and catch, stack unwinding, standard and custom exception types, debugging with GDB and sanitizers, as well as static analysis using clang-tidy and cppcheck.