Lesson ১৫পড়তে ৯ মিনিট লাগবে

টেম্পলেট (Templates)

কোডের জন্য এক ধরনের কুকি কাটার (Cookie cutters) বা ছাঁচ — আকার বা শেপ (shape) একটিই থাকবে, কিন্তু আপনি চাইলে যেকোনো ধরনের ময়ান বা ডো-ই (dough) ব্যবহার করতে পারেন। একবার লিখুন, আর যেকোনো ধরনের বা টাইপের (type) জন্য এর বিভিন্ন ভার্সনগুলো ছাপিয়ে নিন

কুকি কাটার (The Cookie Cutter)

কোনো একটি তারকাকৃতির বা তারা (star) আকারের কুকি কাটারের (cookie cutter) কথা কল্পনা করুন। আপনি এতে চকোলেট, চিনি বা জিনজারব্রেডের (gingerbread) মতো যে ময়ান বা ডো-ই (dough) ব্যবহার করুন না কেন, এর আকারটি কিন্তু তারার মতোই (same shape) থাকে, শুধু এদের ভেতরের উপাদানগুলো ভিন্ন (different material) হয়। সি++ (C++)-এ মূলত এই ব্যাপারটিকেই টেম্পলেট (template) বলা হয়。

এক্ষেত্রে আলাদা আলাদা করে maxInt(), maxDouble(), বা maxString() লেখার পরিবর্তে, আপনি চাইলে এখানকার ঠিক একটি (one) টেমপ্লেট ফাংশনকে ব্যবহার করতে পারেন এবং আপনার কাজের ও উপযুক্ত টাইপের (type) ওপর ভিত্তি করে এর কম্পাইলারটিকে (compiler) তার মতো করে একটি সঠিক ভার্সন (right version) তৈরি বা স্ট্যাম্প (stamp) করার সুযোগ দিতে পারেন। এর জন্য মূলত কোনো প্রকার রানটাইম খরচ বা রানটাইম কস্টের (runtime cost) প্রয়োজন পড়ে না — কারণ এটি মূলত এর কম্পাইল টাইমেই (compile time) সকল সমস্যার সমাধান করে থাকে。

ফাংশন টেমপ্লেট (Function Templates)

ফাংশন টেমপ্লেট (function template) হলো মূলত কোনো কিছুর ব্লুপ্রিন্ট (blueprint) বা নকশা। আপনি চাইলে যেকোনো একটি প্লেসহোল্ডার টাইপের (placeholder type) সাহায্যে এটিকে লিখতে পারেন এবং এরপর এর কম্পাইলারটি (compiler) মূলত এর প্রয়োজন অনুযায়ী বিভিন্ন নির্দিষ্ট (concrete) ফাংশন জেনারেট (generates) করে নেবে。

টেম্পলেট ম্যাক্স (max) ফাংশন

#include <iostream>
#include <string>
using namespace std;
// একটি একক ফাংশন, যা মূলত > সাপোর্ট করে এমন যেকোনো টাইপের (type) সাথেই কাজ করতে পারে
template <typename T>
T myMax(T a, T b) {
return (a > b) ? a : b;
}
int main() {
cout << myMax(3, 7) << endl; // ইন্টিজার ভার্সন বা int version
cout << myMax(3.14, 2.72) << endl; // ডাবল ভার্সন বা double version
cout << myMax<string>("apple", "banana") << endl; // স্ট্রিং ভার্সন বা string version
// প্রয়োজনের সময় এক্সপ্লিসিট বা স্পষ্ট টাইপ (Explicit type):
cout << myMax<double>(3, 2.5) << endl; // জোরপূর্বক বা force ডাবল (double)
return 0;
}
Output
7
3.14
banana
3

টেম্পলেট টাইপের অনুমান বা ডিডাকশন (Template Type Deduction)

সাধারণত আপনাকে নিজের থেকে এর টাইপগুলো লেখার (spell out) প্রয়োজন পড়ে না — এখানকার কম্পাইলারটিই (compiler) মূলত এর আর্গুমেন্টগুলো (arguments) থেকে এগুলোকে বুঝে নেওয়ার বা অনুমান (deduces) করার চেষ্টা করে:

  • myMax(3, 7) — এখানে কম্পাইলার মূলত দুটি int-কে দেখতে পায়, তাই এটি myMax<int>-কে তৈরি বা স্ট্যাম্প (stamps) করে নেয়
  • myMax(3.14, 2.72) — এখানে দুটি double রয়েছে, তাই এটি myMax<double>-কে তৈরি করে নেয়

তবে কখনও কখনও যখন এর টাইপগুলো একে অপরের সাথে সাংঘর্ষিক (conflict) অবস্থায় থাকে (যেমন myMax(3, 2.5) — একটি int এবং একটি double), তখন আপনাকে এটিকে ম্যানুয়ালি বা স্পষ্টভাবে উল্লেখ (specify explicitly) করে দিতে হয়: যেমন myMax<double>(3, 2.5)

ক্লাস টেমপ্লেট (Class Templates)

টেম্পলেট শুধুমাত্র ফাংশনের (functions) জন্যই নয় — আপনি চাইলে সম্পূর্ণ একটি ক্লাসকেও (classes) টেমপ্লেট করতে পারেন। vector<int>, map<string, int>, এবং এসটিএলের (STL) অন্যান্য সব কন্টেইনারগুলো ঠিক এভাবেই কাজ (works) করে থাকে。

টেম্পলেট স্ট্যাক ক্লাস (Template Stack Class)

#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;
template <typename T>
class Stack {
vector<T> data;
public:
void push(const T& val) { data.push_back(val); }
T pop() {
if (data.empty()) throw runtime_error("Stack is empty");
T top = data.back();
data.pop_back();
return top;
}
T peek() const {
if (data.empty()) throw runtime_error("Stack is empty");
return data.back();
}
bool empty() const { return data.empty(); }
size_t size() const { return data.size(); }
};
int main() {
Stack<int> intStack;
intStack.push(10);
intStack.push(20);
intStack.push(30);
cout << "Top: " << intStack.peek() << endl; // টপ বা Top:
cout << "Pop: " << intStack.pop() << endl; // পপ বা Pop:
cout << "Size: " << intStack.size() << endl; // সাইজ বা Size:
Stack<string> strStack;
strStack.push("Hello");
strStack.push("World");
cout << "String top: " << strStack.peek() << endl; // স্ট্রিং টপ বা String top:
return 0;
}
Output
Top: 30
Pop: 30
Size: 2
String top: World

টেম্পলেট স্পেশালাইজেশন (Template Specialization)

মাঝেমধ্যেই এর জেনেরিক ভার্সন (generic version) বা সাধারণ ভার্সনটি কোনো একটি নির্দিষ্ট টাইপের (specific type) ওপরে তেমন ভালোভাবে কাজ করতে পারে না। সেক্ষেত্রে আপনি চাইলে এটিকে স্পেশালাইজ (specialize) বা বিশেষায়িত করতে পারেন — অর্থাৎ, অন্য সব সাধারণ জিনিসের জন্য এর জেনেরিক বা সাধারণ ভার্সনটিকে (generic version) বজায় রেখে ওই নির্দিষ্ট টাইপের জন্য আপনি একটি কাস্টম ইমপ্লিমেন্টেশন (custom implementation) প্রদান করতে পারেন。

স্ট্রিং বা string-এর জন্য স্পেশালাইজেশন (Specialization)

#include <iostream>
#include <string>
using namespace std;
// জেনেরিক বা সাধারণ ভার্সন (Generic version)
template <typename T>
class Printer {
public:
void print(const T& val) {
cout << "Value: " << val << endl; // ভ্যালু বা Value:
}
};
// স্ট্রিং বা string-এর জন্য স্পেশালাইজেশন: বিভিন্ন কোট (quotes) অ্যাড বা যুক্ত (add) করা
template <>
class Printer<string> {
public:
void print(const string& val) {
cout << "String: \"" << val << "\"" << endl; // স্ট্রিং বা String:
}
};
// বুলিয়ান বা bool-এর জন্য স্পেশালাইজেশন: true/false প্রিন্ট (print) করা
template <>
class Printer<bool> {
public:
void print(const bool& val) {
cout << "Bool: " << (val ? "true" : "false") << endl; // বুল বা Bool:
}
};
int main() {
Printer<int> ip;
ip.print(42);
Printer<string> sp;
sp.print("hello");
Printer<bool> bp;
bp.print(true);
return 0;
}
Output
Value: 42
String: "hello"
Bool: true

টেম্পলেটের সাথে auto (সি++১৪ বা C++14+ থকে শুরু)

#include <iostream>
#include <vector>
using namespace std;
// C++14: auto-এর মাধ্যমে কোনো টাইপের রিটার্ন ডিডাকশন (return type deduction) বা অনুমান
template <typename T, typename U>
auto add(T a, U b) {
return a + b; // এর রিটার্ন টাইপটি (return type) মূলত কম্পাইলারই (compiler) নিজে থেকে খুঁজে বা figures out করে নেয়
}
// C++20: সংক্ষিপ্ত ফাংশন টেমপ্লেট (abbreviated function template) (auto প্যারামিটার)
auto multiply(auto a, auto b) {
return a * b;
}
int main() {
cout << add(1, 2.5) << endl; // int + double = double
cout << add(string("Hi "), string("there")) << endl;
cout << multiply(3, 4) << endl; // int * int = int
cout << multiply(2.5, 4) << endl; // double * int = double
return 0;
}
Output
3.5
Hi there
12
10

ভ্যারিয়াডিক টেমপ্লেট (Variadic Templates) (সংক্ষিপ্তভাবে বা Brief)

সি++১১ (C++11) মূলত বেশ কিছু ভ্যারিয়াডিক টেমপ্লেট বা variadic templates চালু বা introduced করেছে — এই টেমপ্লেটগুলো মূলত যেকোনো সংখ্যক বা নাম্বারের (any number) আর্গুমেন্ট গ্রহণ (accept) করতে বেশ পারদর্শী। std::tuple, std::make_unique, এবং printf-এর মতো ফাংশনগুলো ঠিক এভাবেই কাজ করে থাকে।

template <typename... Args>
void print(Args... args) {
    ((cout << args << " "), ...);
    cout << endl;
}
print(1, "hello", 3.14);  // 1 hello 3.14

এখানকার এই ...-টি হলো মূলত একটি প্যারামিটার প্যাক (parameter pack)। আর এর ফোল্ড এক্সপ্রেশনটি বা fold expression-টি ((cout << args << " "), ...) মূলত একে প্রতিটি আর্গুমেন্টের (each argument) জন্য প্রসারিত বা expands করে দেয়。

সি++২০ কনসেপ্ট (C++20 Concepts) (সংক্ষিপ্তভাবে বা Brief)

বিভিন্ন কনসেপ্ট (Concepts) মূলত আপনাকে যেকোনো টেমপ্লেট প্যারামিটারগুলোকে কনস্ট্রেইন (constrain) বা সীমাবদ্ধ করার সুযোগ দেয়, যাতে আপনি পৃষ্ঠার পর পৃষ্ঠা বা অগণিত কোনো ক্রিপ্টিক (cryptic) বা অজানা টেমপ্লেট এররের পরিবর্তে একেবারে স্পষ্ট (clear) কিছু এরর মেসেজ পেতে পারেন:

template <typename T>
  requires std::integral<T>
T gcd(T a, T b) { return b == 0 ? a : gcd(b, a % b); }

gcd(12, 8);    // ওকে বা OK: ইনটিজার (int) মূলত ইনটিগ্রাল বা integral
// gcd(1.5, 2.0); // এরর বা ERROR: ডাবল (double) যে কোনো ইনটিগ্রাল (integral) নয় এমন একটি স্পষ্ট মেসেজ (clear message)

আধুনিক সি++ (C++)-এর ক্ষেত্রে এই কনসেপ্টগুলো (Concepts) হলো কোয়ালিটি অফ লাইফ (quality-of-life) বা জীবনযাত্রার মান উন্নত করার অন্যতম একটি বড় উদাহরণ।

এসএফআইএনএই (SFINAE) (উল্লেখ বা Mention)

কনসেপ্টের (concepts) আগে সি++ (C++) মূলত SFINAE বা এসএফআইএনএই ব্যবহার করত (Substitution Failure Is Not An Error) — এটি হলো এমন একটি নিয়ম যেখানে কোনো একটি টেমপ্লেটের ইনস্ট্যান্সিয়েশন (instantiation) বা তৈরি যদি কোনো কারণে ফেইল করে (fails), তবে তার কম্পাইলারটি (compiler) কোনো এরর (error) না দেখিয়ে নীরবেই (silently) অন্যান্য ওভারলোডগুলোতে (overloads) চেষ্টা (tries) করতে থাকে। এটি বেশ ভালোই কাজ করে, তবে এর লেখার ধরন বা সিনট্যাক্সটি (syntax) অত্যন্ত জঘন্য বা কুৎসিত (ugly)। তাই আপনি যদি সি++২০ (C++20) বা তার পরবর্তী কোনো ভার্সন ব্যবহার করে থাকেন, তবে অবশ্যই কনসেপ্ট (concepts) ব্যবহার করার চেষ্টা করবেন。

Note: টেম্পলেটগুলো (Templates) মূলত কেবল ব্যবহার করার সময়ই কম্পাইল (compiled) করা হয় — তাই এর বিভিন্ন এররগুলো (errors) মূলত সংজ্ঞার বা ডেফিনিশনের (definition) বদলে কল সাইটগুলোতেই (call site) বেশি দেখা যায়। এর ফলে টেমপ্লেটের এরর মেসেজগুলো (error messages) অনেকটাই ক্রিপ্টিক (cryptic) বা বোঝার অনুপযোগী হয়ে ওঠে (কখনও কখনও একটি মাত্র ভুলের জন্য প্রায় শত শত লাইনের এরর বা errors চলে আসে)। তবে C++20-এর কনসেপ্টগুলো (concepts) মূলত বিভিন্ন টাইপকে (types) একদম শুরু থেকেই সীমাবদ্ধ বা constraining করে এবং কিছু স্পষ্ট (clear) ও মানুষের পঠনযোগ্য (human-readable) এরর দিয়ে এর সমাধান বা help করে তুলনামূলক অনেকটাই সহজ করে দিয়েছে।
চ্যালেঞ্জ

ছোট কুইজ

এর কম্পাইলারটি (compiler) মূলত কখন কোনো একটি টেমপ্লেট থেকে কোড জেনারেট (generate code) বা তৈরি করে?
Iterators & Algorithms