Generics and Typing in C++: Why It's Not "Just Templates"?
When a developer transitions from Python or Java to C++, they are often intimidated by the word "template". Associations arise with endless angle brackets, incomprehensible compilation errors, and the magic of std::vector<int>. In reality, generics in C++ (or generic programming) is a powerful tool that allows you to write code once and use it with any data type. And yes, it is not the same as generics in Java or C#.
In this article, we will break down how typing in C++ works through templates, how class templates differ from function templates, and why concepts were added in C++20. There will be many examples — from simple ones to those that are truly useful in production. Let's go!
What are Generics (Templates) in C++?
To put it simply: templates in C++ are a way to write code that works with any data type without losing performance. Unlike Python's dynamic typing, where the type is checked at runtime, C++ generates a separate version of a function or class for each used type at compile time. This is called static typing with parameterization.
Syntactically, a template is declared using the keyword template and angle brackets with parameters:
template <typename T>T max(T a, T b) { return (a > b) ? a : b;}Here, typename T is the template parameter. You can use class instead of typename — there is no difference. When you write max(3, 7), the compiler automatically substitutes int for T and creates the function int max(int, int). If you write max(3.14, 2.71), you get the version for double. Simple and efficient.
Function Templates: The First Step to Generics
Let's start small — function templates in C++. This is an ideal way to avoid code duplication when the logic is the same but the types are different.
Example: a function that prints an element to the console (useful for debugging):
#include <iostream>#include <string>
template <typename T>void print(const T& value) { std::cout << value << std::endl;}
int main() { print(42); // int print(3.1415); // double print("Hello"); // const char* print(std::string("Generics!")); // std::string return 0;}Note: we use const T& — this is typing in C++ with passing by const reference to avoid copying large objects. For built-in types (int, double) this is not critical, but it's a good habit.
What if you need to pass two arguments of different types? Easy:
template <typename T1, typename T2>auto add(T1 a, T2 b) -> decltype(a + b) { return a + b;}
// In C++14 and later you can simply:template <typename T1, typename T2>auto add(T1 a, T2 b) { return a + b;}Here, auto deduces the return type automatically. This is generics in action — one code, an infinite number of type combinations.
Class Templates: Building Universal Structures
Now let's move on to class templates in C++. This is the foundation of the Standard Library (STL): std::vector<T>, std::map<Key, Value>, std::optional<T> — all are implemented using templates.
Let's write a simple but illustrative example — a template class "Pair" (analogous to std::pair):
template <typename First, typename Second>class Pair {private: First first_; Second second_;public: