Generic programming in C++
The design, implementation, and use of broad algorithms are the main focusses of the programming paradigm known as generic programming. Code that is not data type-specific works well on a variety of data types. This style makes programming flexible and reusable.
In C++, generic programming is built on templates. A template is a formula or blueprint used to create functions or classes. Typename T or class T are common placeholder names for types or values that will be specified later when writing generic code using templates. This makes it possible to define a broad range of operations with a single code description.
Template instantiation turns this general blueprint into executable code. The C++ compiler replaces template parameters with appropriate data types or values to construct functions or classes from templates. It is during compilation that this change takes place.
How Template Instantiation Enables Generic Programming
Types are established during compilation in generic programming, as opposed to object-oriented programming (OOP), where types are known at runtime. The terms parametric polymorphism and compile-time polymorphism are frequently used to describe this compile-time type determination.
Function Template Instantiation:
- The “formula from which we can generate type-specific versions of that function” is provided by a function template.
- Often, when a function is invoked, instantiation takes place implicitly. Using the kinds of the arguments supplied in the function call, the compiler “deduces” the type or types of the template parameters.
- The appropriate code for the type of data being utilized is automatically generated by the compiler.
- In a function template call, the type can also be explicitly specified.
Class Template Instantiation:
- To specify a broad class structure, utilise class templates.
- You must explicitly give the template parameter types for a class template in angle brackets (<>) when defining an object of that class type because the compiler typically cannot infer them.
- Upon declaring a particular instance of a class template, the compiler automatically creates all the variables and functions required to handle the actual data.
- Sizes and other constant attributes can be specified by parameterizing class templates on non-type parameters, like integer values.
- The ability to define a specialized implementation for a given type through template specialization can be helpful in situations where the behaviour of the general template is not ideal or appropriate for that type.
Benefits of Generic Programming
Code Reusability: The tedious process of writing distinct implementations for every data type is avoided by writing the code only once, debugging it, and then applying it to any data type.
Efficiency: Because code generation and type resolution occur during compilation, generic code is frequently just as efficient as manually written, type-specific code. It can result in extremely efficient and small code.
Flexibility: A vast range of types can be accepted by algorithms, provided that they satisfy the requirements (concepts) of the algorithm.
Abstraction: Programmers may concentrate on the logic of the method rather than particular data types with the great degree of abstraction that templates offer.
The Standard Template Library (STL) in C++ is a great illustration of generic programming since it offers generic containers, algorithms, and iterators and is heavily based on templates.
Code Example: Generic Functions and Classes
Use template instantiation to demonstrate generic programming with function and class templates and various data types.
#include <iostream>
#include <string>
template <typename T>
int compare(const T &v1, const T &v2) {
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
template <typename T, int N>
class ArrayContainer {
private:
T elts[N];
public:
ArrayContainer() {
for (int i = 0; i < N; ++i) elts[i] = T();
}
T set(const int i, const T val) {
if (i >= 0 && i < N) {
elts[i] = val;
return val;
}
std::cerr << "Error: Index out of bounds in set()!" << std::endl;
return T();
}
T get(const int i) {
if (i >= 0 && i < N) return elts[i];
std::cerr << "Error: Index out of bounds in get()!" << std::endl;
return T();
}
void display() const {
std::cout << "ArrayContainer elements: [";
for (int i = 0; i < N; ++i) std::cout << elts[i] << (i == N - 1 ? "" : ", ");
std::cout << "]" << std::endl;
}
};
int main() {
std::cout << "--- Function Template Instantiation ---\n";
std::cout << "Comparing 1 and 0: " << compare(1, 0) << '\n';
std::cout << "Comparing 3.14 and 2.71: " << compare(3.14, 2.71) << '\n';
std::cout << "Comparing 'a' and 'z': " << compare<char>('a', 'z') << '\n';
std::cout << "Comparing 'C' and 'C': " << compare<char>('C', 'C') << '\n';
std::cout << "\n--- Class Template Instantiation ---\n";
ArrayContainer<int, 5> intArray;
intArray.set(0, 10); intArray.set(2, 30); intArray.set(4, 50);
std::cout << "Value at index 2 in intArray: " << intArray.get(2) << '\n';
intArray.display();
ArrayContainer<std::string, 3> stringArray;
stringArray.set(0, "Hello"); stringArray.set(1, "Generic"); stringArray.set(2, "Programming");
std::cout << "Value at index 1 in stringArray: " << stringArray.get(1) << '\n';
stringArray.display();
ArrayContainer<int, 5> intac;
ArrayContainer<float, 10> floatac;
intac.set(2, 3);
floatac.set(3, 3.5f);
std::cout << "intac.get(2): " << intac.get(2) << '\n';
std::cout << "floatac.get(3): " << floatac.get(3) << '\n';
return 0;
}
Output
--- Function Template Instantiation ---
Comparing 1 and 0: 1
Comparing 3.14 and 2.71: 1
Comparing 'a' and 'z': -1
Comparing 'C' and 'C': 0
--- Class Template Instantiation ---
Value at index 2 in intArray: 30
ArrayContainer elements: [10, 0, 30, 0, 50]
Value at index 1 in stringArray: Generic
ArrayContainer elements: [Hello, Generic, Programming]
intac.get(2): 3
floatac.get(3): 3.5
When the main function calls compare(1, 0), the compiler identifies integer parameters. A function like int compare(const int &v1, const int &v2) instantiates an int comparison version. Invoking compare(3.14, 2.71) creates a compare object. This shows how to reuse generic function declarations across types.
For the class template ArrayContainer, ArrayContainer With the type argument T being int and the non-type parameter N being 5, intArray; instructs the compiler to instantiate a Container class. This creates an ArrayContainer concrete class. An ArrayContainer class is created independently and uniquely by a subsequent declaration such as ArrayContainer stringArray;. The ability of generic programming to provide reusable data structures is demonstrated by this example, which shows how a single class blueprint can produce several distinct class types suited to various data requirements.