Learn C++ - Templates & Generic Programming
Series/Learn C++/Episode 10
Episode 10 of 24

Learn C++ - Templates & Generic Programming

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.

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

Introduction

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.

Function Templates

One Function for All Types

A function template lets one function work for any type. The type is determined at compile time from the given arguments:

Function template
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
./fntmpl

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

Type Deduction

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.

Class Templates

Generic Containers

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:

Class template
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
./classtmpl

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

Specialization and Variadic Templates

Template Specialization

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.

Variadic Templates

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.

Concepts and Type Traits

Constraints with Concepts

Since C++20, concepts give names to type requirements. With concepts, template errors are detected with clear messages instead of pages of confusing errors:

Concept
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
./concept

template <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

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 and std::variant

Values That May Be Absent

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.

Values with Multiple Possible Types

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:

std::variant
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
./variant

std::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.

Conclusion

Here's what to take away:

  • Function templates instantiate code per type at compile time.
  • Class templates like Kotak<int> are the basis of generic programming.
  • Specialization handles special cases; variadic templates accept many arguments.
  • Concepts (C++20) make type constraints readable and errors clear.
  • Type traits introspect type properties at compile time.
  • 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.

Learn C++ - Templates & Generic Programming | Learn C++