Programming Pandit

c/c++/c#/Javav/Python


Latest Update

Friday, October 23, 2026

Generic Classes and Advanced Template Concepts

Generic Classes and Advanced Template Concepts

 

Learning Objectives

After completing this module, students should be able to:

•      Define and use class templates, including member functions defined outside the class.

•      Use several type parameters, non-type parameters and default template arguments.

•      Explain the use of the typename keyword for dependent names.

•      Explain what the export keyword did, why it was removed, and what replaced it.

•      Describe the power of templates: specialization, compile-time computation and generic algorithms.

 

1. Generic Classes

Module 1 showed that a function can be written once for many types. The same idea applies to classes. A generic class (class template) is a class in which one or more data types are left as parameters; the compiler generates an ordinary class from it for every type actually used.

A class template is useful for data structures and containers whose behaviour does not depend on the type of the stored elements: stacks, queues, linked lists, arrays, pairs and so on. Without templates a separate IntStack, DoubleStack and CharStack would be needed.

1.1 Syntax

template <typename T>

class ClassName

{

    T data;                       // a member whose type is the parameter

public:

    void setData(T value);

    T getData() const;

};

 

An object of a class template must name the type argument: ClassName<int> obj;.

Title: A class template is a blueprint; each type argument produces a separate class. - Description: A class template is a blueprint; each type argument produces a separate class.

Figure 2.1: A class template is a blueprint; each type argument produces a separate class.

Important: Box alone is not a type. Box<int> and Box<double> are two different, unrelated classes generated from the same template.

 

1.2 Program: a generic Box class

Program 2.1: A class template with one type parameter.

#include <iostream>

#include <string>

using namespace std;

 

template <typename T>

class Box

{

private:

    T value;

 

public:

    void setValue(T v) { value = v; }

    T getValue() const { return value; }

};

 

int main()

{

    Box<int> b1;

    Box<double> b2;

    Box<string> b3;

 

    b1.setValue(100);

    b2.setValue(25.75);

    b3.setValue("C++ Templates");

 

    cout << "Integer: " << b1.getValue() << endl;

    cout << "Double : " << b2.getValue() << endl;

    cout << "String : " << b3.getValue() << endl;

    return 0;

}

 

Output:

Integer: 100

Double : 25.75

String : C++ Templates

 

1.      template <typename T> makes Box a template, and T is used for the data member and for the parameter and return type of the member functions.

2.      Box<int> b1; makes the compiler create a class in which T is int. Box<string> works in the same way.

3.      getValue() is declared const because it does not change the object (good practice for accessor functions).

 

2. Defining Member Functions Outside the Class

Member functions of a class template can be defined outside the class. Two things are required: the template header must be repeated before each definition, and the class name must be written with its parameter, Sample<T>::.

template <typename T>

return_type ClassName<T>::functionName(parameters)

{

    // body

}

 

Program 2.2: Constructor and member functions defined outside the class template.

#include <iostream>

using namespace std;

 

template <typename T>

class Sample

{

private:

    T value;

 

public:

    Sample(T x);                    // constructor declaration

    void setValue(T x);

    void display() const;

};

 

/* Member functions defined outside: template header + Sample<T>:: */

template <typename T>

Sample<T>::Sample(T x) : value(x) {}

 

template <typename T>

void Sample<T>::setValue(T x)

{

    value = x;

}

 

template <typename T>

void Sample<T>::display() const

{

    cout << "Value: " << value << endl;

}

 

int main()

{

    Sample<int> obj(50);

    obj.display();

    obj.setValue(75);

    obj.display();

 

    Sample<double> d(2.5);

    d.display();

    return 0;

}

 

Output:

Value: 50

Value: 75

Value: 2.5

 

Note: The out-of-class definitions of a template are not ordinary function definitions; they are part of the template. They must therefore stay in the same header file as the class (or in a file included by it), otherwise the compiler cannot instantiate them in other source files and the linker reports undefined reference errors.

 

3. Class Templates with Multiple Type Parameters

A class template may have several type parameters, separated by commas, so that members of different types can be stored together. The Standard Library class std::pair<T1, T2> is such a template.

Program 2.3: A Pair class template.

#include <iostream>

#include <string>

using namespace std;

 

template <typename T1, typename T2>

class Pair

{

private:

    T1 first;

    T2 second;

 

public:

    Pair(T1 a, T2 b) : first(a), second(b) {}

 

    void display() const

    {

        cout << "First value : " << first << endl;

        cout << "Second value: " << second << endl;

    }

};

 

int main()

{

    Pair<int, double> p1(10, 3.14);

    Pair<string, int> p2("Age", 25);

    p1.display();

    p2.display();

 

    // C++17: template arguments deduced (Pair<int, char>)

    Pair p3(7, 'x');

    p3.display();

    return 0;

}

 

Output:

First value : 10

Second value: 3.14

First value : Age

Second value: 25

First value : 7

Second value: x

 

•      Pair<int, double> stores an integer and a floating-point number; Pair<string, int> stores a name and a number.

•      The constructor uses a member initializer list, first(a), second(b), which is the preferred way to initialise members.

 

Version Note: Class template argument deduction (CTAD) is a C++17 feature. In Program 2.3 the line Pair p3(7, 'x'); lets the compiler deduce Pair<int, char> from the constructor arguments.

Before C++17 the template arguments had to be written, Pair<int, char> p3(7, 'x'); (or a helper function such as std::make_pair was used). Compiled with -std=c++14, the line produces:

 

p2_pair.cpp:30:10: error: missing template arguments before ‘p3’

 

4. Non-Type Parameters and Default Template Arguments

Besides type parameters, a template may have non-type parameters: constants whose values are supplied when the template is used, such as an integer size. Template parameters can also have default arguments, just like function parameters.

template <typename T, int SIZE = 5>     // a type parameter and a non-type parameter

class Stack { T arr[SIZE]; ... };

 

Program 2.4: A fixed-size stack whose element type and capacity are template parameters.

#include <iostream>

using namespace std;

 

template <typename T, int SIZE = 5>

class Stack

{

private:

    T arr[SIZE];

    int top;

 

public:

    Stack() : top(-1) {}

 

    void push(T value)

    {

        if (top == SIZE - 1) {

            cout << "Stack Overflow" << endl;

            return;

        }

        arr[++top] = value;

    }

 

    void pop()

    {

        if (top == -1) {

            cout << "Stack Underflow" << endl;

            return;

        }

        cout << "Removed: " << arr[top--] << endl;

    }

 

    void display() const

    {

        if (top == -1) {

            cout << "Stack is empty" << endl;

            return;

        }

        cout << "Stack elements: ";

        for (int i = top; i >= 0; i--)

            cout << arr[i] << " ";

        cout << endl;

    }

};

 

int main()

{

    Stack<int, 5> s;

    s.push(10);

    s.push(20);

    s.push(30);

    s.display();

    s.pop();

    s.display();

 

    Stack<char, 2> cs;                 // a different specialization

    cs.push('a');

    cs.push('b');

    cs.push('c');                      // overflow

 

    Stack<double> ds;                  // default argument: SIZE = 5

    ds.pop();                          // underflow

    return 0;

}

 

Output:

Stack elements: 30 20 10

Removed: 30

Stack elements: 20 10

Stack Overflow

Stack Underflow

 

Title: A stack of three integers: push and pop work at the top. - Description: A stack of three integers: push and pop work at the top.

Figure 2.2: A stack of three integers: push and pop work at the top.

•      The size is a compile-time constant; the array T arr[SIZE] is created without dynamic memory.

•      Stack<int, 5>, Stack<int, 10> and Stack<char, 2> are three different classes.

•      Stack<double> uses the default SIZE = 5.

•      A non-type argument must be a constant expression; a variable cannot be used.

 

Version Note: Non-type parameters may be integers, enumerations, pointers or references in all standards. C++17 adds template <auto N>, where the type of N is deduced; C++20 additionally allows floating-point and certain class types.

 

5. The typename Keyword

The keyword typename has two uses in templates.

5.1 Declaring a type parameter

template <typename T>

void show(T value);

 

Here typename and class are equivalent: template <class T> means exactly the same.

5.2 Naming a dependent type

Inside a template, a name such as Container::value_type depends on the template parameter. Before the template is instantiated the compiler cannot know whether Container::value_type is a type or, for example, a static data member. By default it assumes that it is not a type. To tell the compiler that it is a type, the name is preceded by typename.

Program 2.5: Using typename for dependent type names.

#include <iostream>

#include <vector>

#include <list>

using namespace std;

 

/* Container::value_type is a dependent name that names a type,

   so it must be preceded by the keyword typename. */

template <typename Container>

typename Container::value_type sumAll(const Container &c)

{

    typename Container::value_type total = typename Container::value_type();

    for (typename Container::const_iterator it = c.begin(); it != c.end(); ++it)

        total += *it;

    return total;

}

 

int main()

{

    vector<int> v = {1, 2, 3, 4};

    list<double> l = {1.5, 2.5, 3.0};

 

    cout << "Sum of vector: " << sumAll(v) << endl;

    cout << "Sum of list  : " << sumAll(l) << endl;

    return 0;

}

 

Output:

Sum of vector: 10

Sum of list  : 7

 

Container::value_type is the element type of the container and Container::const_iterator is its iterator type. Without typename the compiler would report an error such as need ‘typename’ before ‘Container::value_type’ because ‘Container’ is a dependent scope. The same function works for vector<int> and list<double>.

Version Note: C++17: typename may also be used in a template template parameter, template <template <typename> typename C>.

C++20 removes the need to write typename in some contexts (for example, the return type of a member function declared in a class), but writing it is still always correct. In examinations, use it before every dependent type name.

 

6. The export Keyword

6.1 The original meaning (C++98)

Templates must normally be fully visible where they are instantiated, so their definitions go into header files. The first C++ standard (C++98) therefore added the keyword export for templates, so that a template could be declared in a header and defined in a separate source file, like an ordinary function:

// legacy C++98 only: exported template definition in a .cpp file

export template <typename T>

T square(T x) { return x * x; }

 

The intention was to separate the interface from the implementation and to reduce compilation time. In practice the feature was extremely hard to implement: only a few compilers ever supported it, and the benefits were small.

Version Note: Removed in C++11. The old meaning of export template was removed from the language by the C++11 standard (the keyword was kept reserved).

Current compilers do not implement it. GCC 13 accepts the line above only to ignore it, with the warnings:

 

warning: keyword 'export' is deprecated, and is ignored

warning: keyword 'export' not implemented, and will be ignored

 

In modern C++ the practical rule is simple: write the full template definition in a header file (or in a file that the header includes). If compile time or code size matters, a template can be explicitly instantiated in one source file for the chosen types, for example template class Stack<int, 10>;.

6.2 The keyword in C++20: modules

Version Note: C++20 reuses the keyword export for modules, which is a completely different feature. export now marks the entities that a module makes available to other files, including templates. Do not confuse it with the removed C++98 export template.

 

A module interface file (math_module.cppm) exporting a template, and the file that imports it:

export module math_module;

 

export template <typename T>

T square(T x)

{

    return x * x;

}

 

#include <iostream>

import math_module;

 

int main()

{

    std::cout << square(5) << " " << square(2.5) << std::endl;

    return 0;

}

 

With GCC 13 these were built with:

g++ -std=c++20 -fmodules-ts -x c++ -c math_module.cppm -o math_module.o

g++ -std=c++20 -fmodules-ts main_mod.cpp math_module.o -o main_mod

 

Output: 25 6.25. Support for modules still varies between compilers and build systems, so most course programs continue to use header files.

7. The Power of Templates

Templates are one of the most powerful features of C++. They allow code reuse, but they also support specialization, computation at compile time and the design of generic libraries.

7.1 Generic data structures and algorithms

The Standard Template Library (STL) is built from class templates (vector, list, map, set) and function templates (sort, find, min_element). One sort works for every container of a type that supports comparison, and one vector stores any type.

7.2 Specialization of class templates

A class template can be specialized for particular types when the generic code is not suitable. A full (explicit) specialization is written for one exact type with template <>; a partial specialization fixes only part of the parameters or a pattern, such as “any pointer type”.

Program 2.6: Primary template, full specialization for bool and partial specialization for pointers.

#include <iostream>

using namespace std;

 

/* Primary template */

template <typename T>

class Printer

{

public:

    void print(const T &v) const { cout << "Value: " << v << endl; }

};

 

/* Full (explicit) specialization for bool */

template <>

class Printer<bool>

{

public:

    void print(const bool &v) const

    {

        cout << "Boolean: " << (v ? "true" : "false") << endl;

    }

};

 

/* Partial specialization for any pointer type T* */

template <typename T>

class Printer<T *>

{

public:

    void print(T *const &p) const

    {

        if (p) cout << "Pointer to: " << *p << endl;

        else   cout << "Null pointer" << endl;

    }

};

 

int main()

{

    int n = 42;

    int *p = &n;

    int *q = nullptr;

 

    Printer<int>().print(7);

    Printer<bool>().print(true);

    Printer<int *>().print(p);

    Printer<int *>().print(q);

    return 0;

}

 

Output:

Value: 7

Boolean: true

Pointer to: 42

Null pointer

 

Use

Template chosen

Reason

Printer<int>

Primary template

No special version exists

Printer<bool>

Full specialization

Exact match for bool: prints true/false

Printer<int *>

Partial specialization Printer<T *>

Matches any pointer; prints the pointed-to value or reports a null pointer

 

Note: Function templates can be fully specialized (Module 1, Section 6.2) but they cannot be partially specialized; for functions, overloading is used instead.

 

7.3 Computation at compile time

Because templates are expanded by the compiler, they can be used to perform calculations during compilation (template metaprogramming). The result is a constant in the compiled program, so there is no run-time cost.

Program 2.7: Compile-time factorial by template recursion, and the modern constexpr alternative; a generic algorithm; an STL algorithm.

#include <iostream>

#include <vector>

#include <algorithm>

#include <string>

using namespace std;

 

/* Compile-time computation with a recursive class template */

template <int N>

struct Factorial

{

    static const int value = N * Factorial<N - 1>::value;

};

 

template <>

struct Factorial<0>

{

    static const int value = 1;

};

 

/* Modern alternative: a constexpr function (C++11 and later) */

constexpr int factorial(int n)

{

    return (n <= 1) ? 1 : n * factorial(n - 1);

}

 

/* One generic algorithm works for every type that supports operator< */

template <typename T>

T smallest(const vector<T> &v)

{

    return *min_element(v.begin(), v.end());

}

 

int main()

{

    cout << "5! (template)  = " << Factorial<5>::value << endl;

    constexpr int f6 = factorial(6);

    static_assert(f6 == 720, "6! must be 720");

    cout << "6! (constexpr) = " << f6 << endl;

 

    vector<int> a = {42, 7, 19};

    vector<string> s = {"pear", "apple", "mango"};

    cout << smallest(a) << endl;

    cout << smallest(s) << endl;

 

    sort(a.begin(), a.end());           // STL algorithm: also a template

    for (int x : a) cout << x << " ";

    cout << endl;

    return 0;

}

 

Output:

5! (template)  = 120

6! (constexpr) = 720

7

apple

7 19 42

 

•      Factorial<5>::value is computed by the compiler: Factorial<5> instantiates Factorial<4>, and so on until the specialization Factorial<0> stops the recursion.

•      static_assert checks a condition at compile time; the program does not compile if it is false.

•      smallest() works for vector<int> and vector<string> without any change.

 

Version Note: C++11: constexpr functions (such as factorial above) and variadic templates (template <typename... Args>) are added. For compile-time calculations constexpr is now preferred to recursive templates.

C++14: variable templates and relaxed constexpr. C++17: if constexpr, fold expressions and class template argument deduction. C++20: concepts and constraints, and modules.

 

7.4 Summary: what templates make possible

Capability

Example

Generic functions

maximum, swapValues, std::sort

Generic containers

Stack<T>, std::vector<T>, std::map<K, V>

Specialization

A special version for bool or for pointers

Compile-time computation

Factorial<5>::value, constexpr

Type safety without run-time cost

Errors found by the compiler; no virtual calls needed

Generic programming style

Algorithms written once for many types, the basis of the STL

 

8. Function Templates versus Class Templates

Feature

Function template

Class template

Purpose

Generic function

Generic class

Syntax

template <typename T> before the function

template <typename T> before the class

Type arguments

Usually deduced from the call; may be explicit

Written explicitly: Box<int> (deduced only from C++17 on)

Partial specialization

Not allowed

Allowed

Typical examples

maximum, swap, sort

Stack, Queue, Pair, vector

 

9. Rules and Common Mistakes

Mistake

Correction

Writing Box b; with no type argument (before C++17)

Write Box<int> b;

Defining out-of-class members without the template header

Repeat template <typename T> and use ClassName<T>::

Putting the definition of a template in a .cpp file

Define templates in headers (export no longer exists)

Forgetting typename before a dependent type

Write typename Container::value_type

Using a variable as a non-type argument

Use a constant expression such as 5 or a constexpr value

Assuming Box<int> and Box<double> are related

They are unrelated types; convert values explicitly

Using a type that lacks an operator needed by the template

Supply the operator, or constrain the template (C++20 concepts)

 

10. Quick Revision

•      Class template = class with type parameters; Box<int> is an instantiation. Out-of-class members need the template header and ClassName<T>::.

•      Several parameters are allowed; non-type parameters and default arguments are allowed (template <typename T, int SIZE = 5>).

•      typename: declares type parameters, and marks dependent type names such as typename C::value_type.

•      export template (C++98) was removed in C++11; in C++20 export is used for modules.

•      Specialization (full and partial for classes) customises behaviour; compile-time computation is possible with templates and constexpr.

 

11. Important Examination Questions

Short-answer questions

1.      What is a generic class? Write its syntax.

2.      How is a member function of a class template defined outside the class?

3.      What is a non-type template parameter? What is a default template argument?

4.      What is the use of the typename keyword? Give an example of a dependent name.

5.      What was the purpose of the export keyword? Why was it removed?

6.      What is template specialization? Differentiate between full and partial specialization.

7.      What is template metaprogramming?

 

Long-answer and programming questions

1.      Explain class templates with a program for a generic stack.

2.      Write a class template Pair<T1, T2> with a constructor and a display function.

3.      Write a program using a class template with a non-type parameter.

4.      Explain the typename and export keywords with examples and mention how they differ between C++ versions.

5.      Explain the power of templates with the STL, specialization and compile-time computation.

6.      Differentiate between function templates and class templates.

 

 


No comments:

Post a Comment