Programming Language & Its Applications β Generic Programming and Exception Handling, NEC licence examination syllabus (Nepal Engineering Council).
Defining member functions outside the class body β the verbose syntax, and why templates must live in headers.
<vector> is 3,000 lines of header with no matching .cpp, and why including a few STL headers turns a 20-line program into a 400 KB binary and a two-second compile. Templates cannot be compiled separately, so every template definition ships as source. That single constraint shapes how all C++ libraries are distributed β header-only libraries like Eigen, nlohmann/json and fmt exist because templates force the issue. Search "why template implementation must be in header" and "C++20 modules template", the second being the long-awaited fix.#include <iostream>
#include <string>
#include <stdexcept>
template <typename T, int CAP = 8>
class Queue {
T data[CAP];
int front, rear, count;
public:
/* declarations only */
Queue();
bool enqueue(const T &v);
bool dequeue(T &out);
const T& peek() const;
void print(const char *tag) const;
/* small ones stay inline */
bool empty() const { return count == 0; }
bool full() const { return count == CAP; }
int size() const { return count; }
/* a nested type, used below */
using value_type = T;
};
/* ---- definitions: note the repeated template header ---- */
template <typename T, int CAP>
Queue<T,CAP>::Queue() : front(0), rear(-1), count(0) {}
template <typename T, int CAP>
bool Queue<T,CAP>::enqueue(const T &v) {
if (full()) return false;
rear = (rear + 1) % CAP; /* circular buffer */
data[rear] = v;
++count;
return true;
}
template <typename T, int CAP>
bool Queue<T,CAP>::dequeue(T &out) {
if (empty()) return false;
out = data[front];
front = (front + 1) % CAP;
--count;
return true;
}
template <typename T, int CAP>
const T& Queue<T,CAP>::peek() const {
if (empty()) throw std::out_of_range("queue is empty");
return data[front];
}
template <typename T, int CAP>
void Queue<T,CAP>::print(const char *tag) const {
std::cout << " " << tag << " [" << count << "/" << CAP << "]: ";
for (int i = 0; i < count; i++)
std::cout << data[(front + i) % CAP] << " ";
std::cout << "\n";
}
int main() {
Queue<int,5> q;
for (int i = 1; i <= 5; i++) q.enqueue(i * 11);
q.print("filled");
std::cout << " enqueue when full: "
<< (q.enqueue(99) ? "ok" : "REJECTED") << "\n";
int v;
q.dequeue(v); q.dequeue(v);
q.print("after 2 dequeues");
std::cout << " last dequeued " << v
<< ", front is now " << q.peek() << "\n";
/* the circular buffer WRAPS β indices reuse slot 0,1 */
q.enqueue(66); q.enqueue(77);
q.print("after 2 more enqueues (wrapped)");
Queue<std::string,3> qs;
qs.enqueue("Ram"); qs.enqueue("Sita");
qs.print("strings");
Queue<int,5>::value_type x = 7; /* nested type alias */
std::cout << " value_type works: " << x << "\n";
Queue<int,2> e;
try { e.peek(); }
catch (const std::out_of_range &ex) {
std::cout << " caught: " << ex.what() << "\n";
}
return 0;
}
Output:
filled [5/5]: 11 22 33 44 55
enqueue when full: REJECTED
after 2 dequeues [3/5]: 33 44 55
last dequeued 22, front is now 33
after 2 more enqueues (wrapped) [5/5]: 33 44 55 66 77
strings [2/3]: Ram Sita
value_type works: 7
caught: queue is empty
template <typename T, int CAP> five times. That verbosity is why most template classes define everything inside the class body β not laziness, but a real readability trade-off. The out-of-class form earns its keep only for long function bodies, where separating declaration from definition makes the class summary readable at a glance.
#include <iostream>
#include <string>
/* Simulating the three-file setup in one file.
In a real project this class body is stack.h ... */
template <typename T>
class Box {
T value;
public:
Box(const T &v);
T get() const;
void set(const T &v);
};
/* ... and these definitions are stack.tpp or stack.cpp */
template <typename T> Box<T>::Box(const T &v) : value(v) {}
template <typename T> T Box<T>::get() const { return value; }
template <typename T> void Box<T>::set(const T &v) { value = v; }
/* EXPLICIT INSTANTIATION: generate code for these NOW.
If the definitions lived in a separate .cpp, these three
lines are what would make Box<int> etc. linkable. */
template class Box<int>;
template class Box<double>;
template class Box<std::string>;
int main() {
Box<int> a(42);
Box<double> b(3.14);
Box<std::string> c("Nepal");
std::cout << " " << a.get() << " " << b.get()
<< " " << c.get() << "\n";
a.set(99); c.set("Kathmandu");
std::cout << " " << a.get() << " " << c.get() << "\n";
/* Box<char> still works HERE because the definitions
are visible in this file. Move them to a .cpp and
Box<char> would fail to link. */
Box<char> d('X');
std::cout << " Box<char> = " << d.get() << "\n";
return 0;
}
Output:
42 3.14 Nepal
99 Kathmandu
Box<char> = X
#include <iostream>
#include <string>
#include <cstring>
template <typename T>
class Container {
T items[10];
int n;
public:
Container() : n(0) {}
void add(const T &v) { if (n < 10) items[n++] = v; }
int count() const { return n; }
/* the generic implementation */
bool contains(const T &key) const;
void describe() const;
};
template <typename T>
bool Container<T>::contains(const T &key) const {
for (int i = 0; i < n; i++) if (items[i] == key) return true;
return false;
}
template <typename T>
void Container<T>::describe() const {
std::cout << " generic container of " << n << " items: ";
for (int i = 0; i < n; i++) std::cout << items[i] << " ";
std::cout << "\n";
}
/* SPECIALISE ONE MEMBER for const char*, where == compares
ADDRESSES and would be wrong. Note: no template<typename T>
header β the type is fixed, so it is template<> */
template <>
bool Container<const char*>::contains(const char *const &key) const {
for (int i = 0; i < n; i++)
if (std::strcmp(items[i], key) == 0) return true;
return false;
}
template <>
void Container<const char*>::describe() const {
std::cout << " C-string container of " << n << ": ";
for (int i = 0; i < n; i++) std::cout << "\"" << items[i] << "\" ";
std::cout << "\n";
}
int main() {
std::cout << std::boolalpha;
Container<int> ci;
ci.add(10); ci.add(20); ci.add(30);
ci.describe();
std::cout << " contains(20)? " << ci.contains(20) << "\n";
std::cout << " contains(99)? " << ci.contains(99) << "\n";
Container<const char*> cs;
cs.add("Ram"); cs.add("Sita");
cs.describe();
/* a DIFFERENT buffer holding the same text β the
generic == would compare pointers and say false */
char buf[10]; std::strcpy(buf, "Sita");
std::cout << " contains(other \"Sita\" buffer)? "
<< cs.contains(buf) << " (strcmp, correct)\n";
std::cout << " contains(\"Hari\")? "
<< cs.contains("Hari") << "\n";
return 0;
}
Output:
generic container of 3 items: 10 20 30
contains(20)? true
contains(99)? false
C-string container of 2: "Ram" "Sita"
contains(other "Sita" buffer)? true (strcmp, correct)
contains("Hari")? false
-ftime-trace in Clang produces a flame graph showing exactly which template instantiations are eating your build time β running it once on a real project is genuinely eye-opening. Search "clang ftime-trace template instantiation".template <typename T> then ReturnType ClassName<T>::member(...). Forgetting either the template header or the <T> on the class name is the standard error to be able to name. The high-value question is why template definitions must appear in the header: the compiler needs the definition at the point of instantiation, so separate compilation produces undefined-reference linker errors β and be ready to give explicit instantiation (template class Box<int>;) as the workaround.Create a free account to tick topics off, take notes as you read, watch the video lessons and get a day-by-day study plan built around your exam date.
Loadingβ¦