সরাসরি পর্বে যান
পর্ব ৪১ · ৫৫-এর মধ্যে · মডিউল ৬

লাইব্রেরি ভ্রমণ — chrono, random আর filesystem

A Tour of the Standard Library — chrono, random, filesystem and more

Taught in Bangla, this lesson tours the C++ standard library: a map of headers, std::chrono durations, time points and clocks, C++20 calendar and time zones, replacing rand() with <random> engines and distributions, small utilities such as gcd, midpoint and <bit>, from_chars and a first look at filesystem.

এই পর্বে যা শিখবেন

  • standard library-র মানচিত্র: কোন header পরিবারে কী আছে, আর এই কোর্সের কোন পর্বে তা শেখানো হয়
  • std::chrono: duration, time_point, clock, duration_cast, আর কোন ঘড়ি দিয়ে সময় মাপতে হয়
  • C++20 ক্যালেন্ডার দিয়ে তারিখের হিসাব, অবৈধ তারিখ চেনা, আর time zone-এর মূল ধারণা
  • <random>: কেন rand() নয়, engine ও distribution, seed, পুনরাবৃত্তিযোগ্য ফল আর নিরাপত্তার সীমা
  • from_chars, numeric ও bit-এর ছোট যন্ত্র, utility-র টুকিটাকি, filesystem-এর এক ঝলক — আর এসব জায়গায় AI-এর ভুল ধরা

১ · নিজে লেখার আগে বাক্সটা খুলুন

ধরুন আপনাকে একটি পরীক্ষার প্রস্তুতির অ্যাপ বানাতে বলা হলো। অ্যাপটি দেখাবে পরীক্ষার আর কত দিন বাকি, প্রতিদিন প্রশ্নগুলো এলোমেলো ক্রমে দেবে, একটি CSV ফাইল থেকে নম্বর পড়বে, আর মাপবে একজন পরীক্ষার্থী প্রতিটি প্রশ্নে কত সময় নিলেন। প্রথম চিন্তা হতে পারে: “দিন গোনার একটি ফাংশন লিখি, এলোমেলো করার জন্য rand() নিই, সংখ্যা পড়তে atoi ডাকি।” প্রতিটি কাজই ছোট মনে হয়। কিন্তু প্রতিটির ভেতরে লুকিয়ে আছে এমন ফাঁদ, যা বহু প্রোগ্রামার বছরের পর বছর ধরে খুঁজে পেয়েছেন — অধিবর্ষ (leap year), ৩১ দিনের মাস, ঘড়ি হঠাৎ পিছিয়ে যাওয়া, এলোমেলো সংখ্যার পক্ষপাত, “8x” লেখাকে 8 পড়ে ফেলা।

ঢাকায় বাড়ি বানাতে গিয়ে কেউ নিজের ইট নিজে পোড়ান না, নিজের হাতে স্ক্রু বানান না। নবাবপুরের হার্ডওয়্যার বাজারে গিয়ে মাপমতো, মানসম্মত জিনিস কিনে আনেন — কারণ কারখানায় সেগুলো লাখবার তৈরি হয়ে পরীক্ষিত। কিন্তু বাজারে কী কী পাওয়া যায় তা না জানলে আপনি হয়তো নিজেই একটি খারাপ স্ক্রু বানাতে বসে যাবেন। standard library হলো C++-এর সেই বাজার — আর আজকের পর্ব তার একটি হাঁটাপথের ভ্রমণ।

C++-এর standard library প্রতিটি compiler-এর সাথে আসে — GCC-র সাথে libstdc++, Clang-এর সাথে প্রায়ই libc++, Visual Studio-র সাথে MSVC STL. এর চারটি বড় গুণ আছে:

  • পরীক্ষিত: কোটি কোটি প্রোগ্রাম এগুলো প্রতিদিন ব্যবহার করে। আপনার হাতে লেখা তারিখ-গণনায় যে বাগ আছে, তা এখানে বহু আগেই ধরা পড়ে ঠিক হয়েছে।
  • সব জায়গায় চলে (portable): একই কোড Windows, Linux, macOS আর অনলাইন judge-এ একই অর্থ বহন করে। standard যা নিশ্চিত করে, তা সব বাস্তবায়নকে মানতে হয়।
  • দ্রুত: লাইব্রেরি-লেখকেরা বছরের পর বছর এগুলো অপ্টিমাইজ করেন। std::from_chars বা std::sort-এর চেয়ে দ্রুত সংস্করণ হাতে লেখা সহজ নয়।
  • সবাই চেনে: অন্য একজন C++ প্রোগ্রামার std::chrono::steady_clock দেখলেই বোঝেন আপনি কী করছেন। আপনার নিজের বানানো myTimer2 বুঝতে তাঁকে পুরো ফাংশন পড়তে হবে।
মূল ধারণা

এই পর্বের লক্ষ্য প্রতিটি ফাংশন মুখস্থ করানো নয়। লক্ষ্য হলো আপনার মাথায় একটি মানচিত্র বসিয়ে দেওয়া: “সময়ের কাজ? <chrono>-এ খুঁজি। এলোমেলো সংখ্যা? <random>. লেখা থেকে সংখ্যা? <charconv>.” খুঁটিনাটি cppreference.com-এ সবসময় পাবেন; কিন্তু কী খুঁজতে হবে, সেটা না জানলে খোঁজা শুরুই হয় না।

২ · লাইব্রেরির মানচিত্র — কোন header-এ কী

standard library শতাধিক header-এ ভাগ করা। এগুলোকে কয়েকটি পরিবারে সাজালে মনে রাখা সহজ হয়। নিচের টেবিলে প্রতিটি পরিবার, তার প্রধান header, আর এই কোর্সের কোথায় সেটি শেখানো হয়েছে বা হবে:

পরিবারপ্রধান headerকী দেয়কোর্সে
Container<vector>, <array>, <deque>, <map>, <unordered_map>ডেটা রাখার নানা কাঠামোপর্ব ১৩, ৩০, ৩১
Algorithm ও iterator<algorithm>, <iterator>, <ranges>sort, find, transform, viewsপর্ব ৩২, ৩৩, ৩৫
লেখা ও format<string>, <string_view>, <format>, <print>, <charconv>string, ফরম্যাট করা আউটপুট, সংখ্যা↔লেখাপর্ব ৭, ১৪, এই পর্ব
Utility<utility>, <tuple>, <memory>, <optional>, <variant>, <expected>, <functional>pair, smart pointer, optional, functionপর্ব ১২, ১৭, ৩৪, ৩৭, ৩৮
সময়<chrono>duration, clock, ক্যালেন্ডার, time zoneএই পর্ব
এলোমেলো সংখ্যা<random>engine, distribution, seedএই পর্ব (shuffle: পর্ব ৩৩)
সংখ্যার হিসাব<cmath>, <numeric>, <numbers>, <bit>, <limits>গণিতের ফাংশন, gcd, ধ্রুবক, bit-এর কাজপর্ব ৫, এই পর্ব
Input/Output<iostream>, <fstream>, <sstream>কনসোল, ফাইল আর string streamপর্ব ৪, ৭, ৪৪
Filesystem<filesystem>পথ, ফোল্ডার, ফাইলের তথ্যএক ঝলক এখানে, বিস্তারিত পর্ব ৪৪
Concurrency<thread>, <mutex>, <atomic>, <future>একসাথে একাধিক কাজ চালানোপর্ব ৪৫, ৪৬
Error ও টাইপ<stdexcept>, <system_error>, <type_traits>, <concepts>exception, error code, টাইপের প্রশ্নপর্ব ৩৭, ৩৯, ৪০

টেবিলটি দেখলে একটি জিনিস চোখে পড়ে: বেশিরভাগ পরিবার আপনি ইতিমধ্যে চেনেন। আজ আমরা ঘুরব সেই কোণগুলোতে, যেগুলো আলাদা পর্ব পায়নি, কিন্তু প্রায় প্রতিটি বাস্তব প্রোগ্রামে লাগে — সময়, এলোমেলো সংখ্যা, সংখ্যার ছোট যন্ত্র আর লেখা থেকে সংখ্যা পড়া।

C জানলে

C-এর header-গুলো C++-এও আছে, শুধু নাম বদলে: <math.h> হয়েছে <cmath>, <time.h> হয়েছে <ctime>, আর ফাংশনগুলো std::-এর ভেতরে। পার্থক্য হলো, C++ এদের পাশে টাইপ-নিরাপদ বিকল্প দিয়েছে: time_t-এর জায়গায় chrono, rand()-এর জায়গায় <random>, strtol-এর জায়গায় from_chars. C-এর লাইব্রেরি কীভাবে কাজ করে আর কোথায় ফাঁদ, তা দেখতে C কোর্সের Standard Library পর্ব পড়ুন।

৩ · chrono-র duration — একক যেখানে টাইপের অংশ

পুরোনো অনেক API-তে সময় যেত একটি সাধারণ সংখ্যা হিসেবে: sleep(5). পাঁচ কী? সেকেন্ড, মিলিসেকেন্ড, নাকি মাইক্রোসেকেন্ড? উত্তর লুকিয়ে থাকে ডকুমেন্টেশনে, কোডে নয়। একজন ভাবলেন মিলিসেকেন্ড, আরেকজন সেকেন্ড — আর প্রোগ্রাম হাজার গুণ বেশি বা কম অপেক্ষা করল। compiler কিছুই বলবে না, কারণ তার চোখে দুটিই শুধু int.

এককের এমন গোলমাল কত দামি হতে পারে, তার বিখ্যাত উদাহরণ NASA-র Mars Climate Orbiter. ১৯৯৯ সালে মহাকাশযানটি মঙ্গলের কক্ষপথে ঢোকার সময় হারিয়ে যায়। পরে তদন্তে দেখা যায়, একটি সফটওয়্যার অংশ ধাক্কার মান দিচ্ছিল ইংরেজি এককে (pound-force second), আর অন্য অংশ ধরে নিয়েছিল মেট্রিক একক (newton-second). সংখ্যাগুলো ঠিকই ছিল — শুধু একক লেখা ছিল না।

std::chrono-র উত্তর হলো এককটিকে টাইপের ভেতরে ঢুকিয়ে দেওয়া। একটি duration হলো একটি সংখ্যা (count) আর একটি একক (period) — যেমন “১৫০০টি মিলিসেকেন্ড”। std::chrono::seconds, milliseconds, minutes, hours সবই আলাদা টাইপ, আর C++20 থেকে days, weeks, months, years-ও আছে। ভেতরে এরা শুধু একটি পূর্ণসংখ্যা — কোনো বাড়তি মেমরি বা গতির খরচ নেই, এককের সব হিসাব compile-এর সময়েই হয়।

C++14 থেকে লেখাও সহজ: literal দিয়ে 5s, 250ms, 2h, 30min. এগুলো ব্যবহার করতে using namespace std::chrono; (বা শুধু std::chrono_literals) লিখতে হয় — তবে একটি ফাংশনের ভেতরে, header ফাইলের global অংশে নয়।

void wait_for_otp(std::chrono::seconds timeout);  // একক স্বাক্ষরেই লেখা

using namespace std::chrono;
wait_for_otp(90s);          // ঠিক
wait_for_otp(2min);         // ঠিক: 2min নিজে থেকেই 120s হয়
// wait_for_otp(90);        // error: শুধু একটি int — কোন একক?
// wait_for_otp(1500ms);    // error: ms থেকে s-এ গেলে তথ্য হারাতে পারে

শেষ দুটি লাইনই এই নকশার মূল কথা। যে রূপান্তরে তথ্য হারায় না, তা নিজে থেকে হয় — মিনিট থেকে সেকেন্ড, সেকেন্ড থেকে মিলিসেকেন্ড। যে রূপান্তরে তথ্য হারাতে পারে, তা compiler আটকায় — ১৫০০ মিলিসেকেন্ডকে পূর্ণ সেকেন্ডে আনলে আধা সেকেন্ড কোথায় যাবে? সেটি আপনাকে স্পষ্ট করে বলতে হবে: duration_cast শূন্যের দিকে কেটে ফেলে (truncate), আর C++17-এর floor, ceil, round নিজেদের নাম অনুযায়ী কাজ করে। ধাপে ধাপে দেখুন — প্রতিটি ধাপে আগে নিজে অনুমান করুন:

1auto t = 1500ms;2milliseconds m = 2s;3seconds s = t;            // ?4auto a = duration_cast<seconds>(t);5auto b = round<seconds>(t);6auto c = 2h + 30min;
এই লাইনের ফল
টাইপ
ভেতরের count
রূপান্তর
  1. একটি duration = count + একক। 1500ms-এর ভেতরে শুধু 1500 সংখ্যাটি আছে; “মিলিসেকেন্ড” কথাটি আছে টাইপে। মেমরিতে এটি একটি সাধারণ long long-এর সমান।
  2. ক্ষতিহীন রূপান্তর নিজে থেকে। ২ সেকেন্ডকে মিলিসেকেন্ডে আনতে শুধু ১০০০ দিয়ে গুণ — কিছুই হারায় না। তাই compiler কোনো cast ছাড়াই 2s-কে 2000ms বানিয়ে দেয়।
  3. ক্ষতিকর রূপান্তর নিষেধ। ১৫০০ মিলিসেকেন্ড = ১.৫ সেকেন্ড, কিন্তু seconds শুধু পূর্ণসংখ্যা রাখে। আধা সেকেন্ড নীরবে ফেলে দেওয়ার বদলে compiler error দেয়। এই error-টাই chrono-র সবচেয়ে বড় উপহার।
  4. duration_cast — “আমি জানি, কেটে দাও।” ফল 1s; ০.৫ সেকেন্ড বাদ। কাটা হয় শূন্যের দিকে, তাই -1500ms দিলে আসে -1s. ঋণাত্মক মানে নিচের দিকে নামাতে চাইলে floor লিখুন — সেটি দেয় -2s.
  5. round — নিকটতম মান। ১.৫ ঠিক মাঝখানে; এমন ক্ষেত্রে round জোড় সংখ্যাটি বেছে নেয়, তাই 2s (আর 2500ms দিলেও 2s). ওপরের দিকে নিতে চাইলে ceil.
  6. দুই এককের যোগ। ঘণ্টা আর মিনিট যোগ করলে ফল এমন একটি এককে আসে, যাতে দুটিই নির্ভুল ধরা যায় — এখানে মিনিট। 2h + 30min = 150min. আপনি কোথাও ৬০ দিয়ে গুণ করেননি; ভুল হওয়ার সুযোগও রাখেননি।

ভগ্নাংশ দরকার হলে দশমিক count-ওয়ালা duration নিন: duration<double> মানে দশমিক সেকেন্ড, তাই duration<double>(1500ms) প্রিন্ট করলে আসে 1.5s. আর কাঁচা সংখ্যাটি দরকার হলে (যেমন JSON-এ লিখতে) .count() ডাকুন — কিন্তু যত দেরিতে সম্ভব, যাতে একক যতক্ষণ সম্ভব টাইপের পাহারায় থাকে।

৪ · time_point আর clock — কোন ঘড়ি দিয়ে মাপবেন

duration হলো “কতক্ষণ”। আর time_point হলো “কখন” — একটি নির্দিষ্ট মুহূর্ত, যা প্রকাশ করা হয় একটি শুরুর বিন্দু (epoch) থেকে কত duration পেরিয়েছে তা দিয়ে। প্রতিটি time_point কোনো একটি ঘড়ির (clock) সাথে বাঁধা, আর দুটি time_point বিয়োগ করলে পাওয়া যায় একটি duration. দুটি ঘড়ি সবচেয়ে জরুরি:

system_clock — দেয়ালঘড়ি

কম্পিউটারের আসল তারিখ ও সময়। C++20 থেকে এর epoch নির্দিষ্ট: ১ জানুয়ারি ১৯৭০, UTC (Unix time). ক্যালেন্ডারে রূপান্তর করা যায়। কিন্তু ঘড়িটি বদলাতে পারে — ব্যবহারকারী সময় ঠিক করলেন, বা ইন্টারনেট থেকে সময় মেলানো (NTP) হলো।

লগ, রসিদ, তারিখ দেখানো

steady_clock — স্টপওয়াচ

এই ঘড়ি কখনো পেছনে যায় না (monotonic) — প্রতিটি নতুন রিডিং আগেরটির সমান বা বেশি। এর epoch অনির্দিষ্ট (প্রায়ই কম্পিউটার চালু হওয়ার সময়), তাই এ থেকে “আজ কত তারিখ” জানা যায় না।

সময় মাপা, timeout, benchmark

তৃতীয় একটি নামও দেখবেন — high_resolution_clock. নামটি লোভনীয়, কিন্তু standard বলে এটি অন্য দুটির কোনো একটির ডাকনাম হতে পারে; কোনটি, তা লাইব্রেরিভেদে আলাদা। তাই এটি এড়িয়ে চলুন। নিয়মটা এক লাইনের: কতক্ষণ লাগল জানতে steady_clock, কখন ঘটল জানতে system_clock. কেন, তা নিচের ঘটনায় দেখুন:

1auto w0 = system_clock::now();2auto s0 = steady_clock::now();3work();                  // 1.5 s4// NTP: wall clock moved back 3 s5auto w = system_clock::now() - w0;6auto s = steady_clock::now() - s0;
দুই ঘড়ির রিডিং (উদাহরণের মান)
system_clock
steady_clock
মাপা সময়
  1. দুই ঘড়ি একসাথে চালু। দেয়ালঘড়ি বলছে সকাল ১০টা। steady_clock-এর সংখ্যাটির কোনো ক্যালেন্ডার-অর্থ নেই — ধরুন কম্পিউটার চালু হওয়ার পর ৫০০০ সেকেন্ড। এখানে সংখ্যাগুলো শুধু উদাহরণ।
  2. কাজ চলল দেড় সেকেন্ড। দুই ঘড়িই দেড় সেকেন্ড এগোল। এখন পর্যন্ত কোনো পার্থক্য নেই — তাই ছোট পরীক্ষায় system_clock দিয়ে মাপলেও ভুল চোখে পড়ে না।
  3. মাঝপথে সময় মেলানো। কম্পিউটারের ঘড়ি ৩ সেকেন্ড এগিয়ে ছিল; NTP সেটি ঠিক করল, দেয়ালঘড়ি পিছিয়ে গেল। steady_clock-কে কেউ ছোঁয় না — তার কাজই হলো শুধু সামনে চলা।
  4. ফল। system_clock-এ মাপা সময় ঋণাত্মক: −১.৫ সেকেন্ড! steady_clock সঠিক বলছে ১.৫ সেকেন্ড। benchmark, timeout বা “৩০ সেকেন্ড পর OTP বাতিল” — যেকোনো সময়কাল মাপায় দেয়ালঘড়ি ব্যবহার করলে এমন বাগ মাসে একবার দেখা দেবে, আর কেউ বুঝবে না কেন।

একটি কাজ কতক্ষণ নিল, তা মাপার স্বাভাবিক কাঠামো এমন। এর আউটপুট প্রতিবার আলাদা, তাই এটি চালানোর উদাহরণ হিসেবে দেওয়া হয়নি:

#include <chrono>
#include <print>

void build_question_bank();                       // যে কাজ মাপব

void measure() {
    using clock = std::chrono::steady_clock;
    const auto start = clock::now();
    build_question_bank();
    const auto elapsed = clock::now() - start;    // একটি duration
    std::println("took {}",
        std::chrono::duration_cast<std::chrono::milliseconds>(elapsed));
}   // যেমন: took 37ms — প্রতিবার আলাদা

একবার মেপেই সিদ্ধান্ত নেবেন না। প্রথমবার চালালে cache ঠান্ডা থাকে, অন্য প্রোগ্রাম CPU নিতে পারে, আর অপ্টিমাইজার কখনো কখনো এমন কাজ পুরো মুছে দেয় যার ফল কোথাও ব্যবহৃত হয়নি — তখন “০ মিলিসেকেন্ড” দেখে খুশি হওয়ার কিছু নেই। কাজটি কয়েকবার চালান, ফল ব্যবহার করুন, আর -O2 দিয়ে compile করে মাপুন। ঠিকভাবে মাপার পুরো পদ্ধতি পর্ব ৪৯-এ।

৫ · C++20 ক্যালেন্ডার আর time zone — তারিখের হিসাব

তারিখের হিসাব শুনতে সহজ: “আজ থেকে পরীক্ষার দিন পর্যন্ত কত দিন?” হাতে লিখতে গেলে শুরু হয় প্রশ্নের পর প্রশ্ন। কোন মাস ৩০ দিনের, কোনটি ৩১-এর? ফেব্রুয়ারি এ বছর ২৮ না ২৯? ২১০০ সাল কি অধিবর্ষ? (না — ১০০ দিয়ে বিভাজ্য, কিন্তু ৪০০ দিয়ে নয়।) AI আর নতুন প্রোগ্রামাররা প্রায়ই “এক মাস = ৩০ দিন” ধরে নেন, আর ফল কয়েক দিন এদিক-ওদিক হয়ে যায়।

C++20 chrono-তে পুরো ক্যালেন্ডার যোগ করেছে। মূল টাইপগুলো:

  • year, month, day, weekday — আলাদা আলাদা অংশ। literal: 2026y, 17d; মাসের নাম ধ্রুবক হিসেবে: October, February.
  • year_month_day — একটি পূর্ণ তারিখ, তিনটি ঘরে (field) রাখা। লেখা যায় / দিয়ে: 2026y/October/17 বা 2026y/10/17.
  • sys_days — একই তারিখ, কিন্তু ১৯৭০-এর ১ জানুয়ারি থেকে কত দিন, সেই একটি সংখ্যা হিসেবে। এটি আসলে system_clock-এর একটি time_point, যার একক দিন।

দুটি রূপের দুই রকম কাজ। মানুষের মতো তারিখ পড়তে-লিখতে year_month_day; দিন গুনতে আর যোগ-বিয়োগ করতে sys_days. দুটির মধ্যে রূপান্তর এক লাইনে, আর ভেতরের অধিবর্ষ আর মাসের দৈর্ঘ্যের হিসাব লাইব্রেরি নিজে করে। আরেকটি জরুরি ফাংশন হলো ok() — তারিখটি বাস্তবে আছে কি না, তা জানায়। এবার ক্যালেন্ডারের হিসাবের ফাঁদগুলো ধাপে ধাপে দেখুন:

শুরুর তারিখyear_month_day
auto d = 2026y / January / 31;
d          => 2026-01-31
d.ok()     => true
এক মাস যোগশুধু মাসের ঘর বদলায়
auto n = d + months{1};
n          => 2026-02-31 is not a valid date
n.ok()     => false
sys_days-এ রূপান্তরবাড়তি দিন পরের মাসে গড়ায়
year_month_day{ sys_days{n} }
           => 2026-03-03
(ফেব্রুয়ারি ২৮ + আরও ৩ দিন)
মাসের শেষ দিন চাইলে/last
year_month_day{ 2026y / February / last }
           => 2026-02-28
(2028y / February / last  => 2028-02-29)
বৈধতা যাচাইok()
(2026y / February / 30).ok()  => false
(2028y / February / 29).ok()  => true    // অধিবর্ষ
(2100y / February / 29).ok()  => false   // ১০০-তে বিভাজ্য, ৪০০-তে নয়
  1. একটি স্বাভাবিক তারিখ। ৩১ জানুয়ারি ২০২৬. year_month_day এটিকে তিনটি ঘরে রাখে — বছর, মাস, দিন — ঠিক যেভাবে মানুষ লেখে। ok() বলছে তারিখটি বৈধ।
  2. মাস যোগ করলে শুধু মাসের ঘর বদলায়। months{1} যোগ করায় মাস হলো ফেব্রুয়ারি, দিন রইল ৩১. এমন তারিখ পৃথিবীতে নেই, তাই ok() হলো false. লাইব্রেরি নিজে থেকে কিছু “ঠিক” করে দেয়নি — কারণ কোনটি ঠিক, তা আপনার ব্যবসার নিয়মের ওপর নির্ভর করে।
  3. একটি নীতি: বাড়তি দিন পরের মাসে। অবৈধ তারিখকে sys_days-এ নিলে দিনগুলো গুনে নেওয়া হয়: ফেব্রুয়ারির ২৮ দিন পেরিয়ে আরও ৩ দিন, অর্থাৎ ৩ মার্চ। মাসিক কিস্তির হিসাবে এটা সম্ভবত ভুল ফল।
  4. আরেক নীতি: মাসের শেষ দিন। “প্রতি মাসের শেষ দিনে বিল” — এমন নিয়মে /last লিখুন। লাইব্রেরি নিজে জানে ২০২৬-এর ফেব্রুয়ারি ২৮ দিনের, আর ২০২৮-এর ফেব্রুয়ারি ২৯ দিনের।
  5. ব্যবহারকারীর দেওয়া তারিখ সবসময় যাচাই করুন। একটি ফর্মে কেউ ৩০ ফেব্রুয়ারি লিখলে ok() সেটি ধরে ফেলে। অধিবর্ষের পুরো নিয়মও লাইব্রেরির ভেতরে — ২১০০ সালের ২৯ ফেব্রুয়ারি নেই, আর আপনাকে সেটা মনে রাখতে হয়নি।

এবার সব একসাথে একটি ছোট প্রোগ্রামে দেখুন। আগের অংশের duration-এর হিসাব, আর একটি পরীক্ষার দিন গোনা — ধরুন ঢাকায় একটি চাকরির পরীক্ষা ১০ ডিসেম্বর ২০২৬, আর আজকের তারিখ আমরা কোডেই স্থির করে দিয়েছি, যাতে আউটপুট সব জায়গায় এক থাকে:

#include <chrono>
#include <print>

int main() {
    using namespace std::chrono;
    auto t = 1500ms;
    std::println("{} -> cast {}, round {}", t, duration_cast<seconds>(t), round<seconds>(t));
    std::println("2h + 30min = {}", 2h + 30min);

    constexpr year_month_day today{2026y / October / 17};    // fixed for the demo
    constexpr year_month_day exam{2026y / December / 10};
    std::println("{} is a {}", today, weekday{sys_days{today}});
    std::println("days left: {}", sys_days{exam} - sys_days{today});

    auto feb = 2026y / February / 30;
    std::println("{} ok? {}", feb, feb.ok());
    auto next = 2026y / January / 31 + months{1};
    std::println("Jan 31 + 1 month ok? {}", next.ok());
}

আউটপুট ছয় লাইন: 1500ms -> cast 1s, round 2s, 2h + 30min = 150min, 2026-10-17 is a Sat, days left: 54d, 2026-02-30 is not a valid date ok? false, আর Jan 31 + 1 month ok? false. খেয়াল করুন, std::println duration আর তারিখ নিজেই সুন্দর করে প্রিন্ট করে — একক (ms, min, d) সহ। দুটি তারিখকে sys_days-এ নিয়ে বিয়োগ করায় পাওয়া গেল দিনের একটি duration: ৫৪ দিন (অক্টোবরের বাকি ১৪, নভেম্বরের ৩০, ডিসেম্বরের ১০)। কোথাও “মাস = ৩০ দিন” ধরা হয়নি। অবৈধ তারিখ প্রিন্ট করলে লাইব্রেরি নিজেই লিখে দেয় যে তারিখটি বৈধ নয়। Sat আসে ডিফল্ট “C” locale থেকে।

আসল “আজ” — আর time zone-এর প্রশ্ন

বাস্তব অ্যাপে “আজ” আসবে ঘড়ি থেকে: floor<days>(system_clock::now()). কিন্তু এখানে একটি ফাঁদ আছে। system_clock চলে UTC-তে, আর ঢাকা UTC-র ৬ ঘণ্টা আগে। অর্থাৎ ঢাকায় রাত ১২টা থেকে সকাল ৬টার মধ্যে UTC-তে তখনো আগের দিন! রাত ২টায় কেউ অ্যাপ খুললে দেখবেন “৫৫ দিন বাকি”, অথচ ঢাকার ক্যালেন্ডারে ইতিমধ্যে নতুন দিন। সমাধান C++20-এর time zone সহায়তা:

using namespace std::chrono;
zoned_time dhaka{"Asia/Dhaka", system_clock::now()};    // IANA-র নাম
auto local_today = floor<days>(dhaka.get_local_time());
year_month_day ymd{local_today};                         // ঢাকার তারিখ
std::println("{:%d/%m/%Y %H:%M}", dhaka);                // যেমন 17/10/2026 21:05

"Asia/Dhaka" নামটি আসে IANA time zone database থেকে — বিশ্বের প্রায় সব অপারেটিং সিস্টেম যে তালিকা ব্যবহার করে, যেখানে প্রতিটি অঞ্চলের ইতিহাসসহ নিয়ম লেখা। “শুধু ৬ ঘণ্টা যোগ করি” কেন যথেষ্ট নয়? কারণ নিয়ম বদলায়: বাংলাদেশ নিজেই ২০০৯ সালে কয়েক মাসের জন্য ঘড়ি এক ঘণ্টা এগিয়ে রেখেছিল (daylight saving time), আর পুরোনো তারিখের হিসাবে সেটি ধরতে হয়। তবে একটি সতর্কতা: time zone অংশের সহায়তা লাইব্রেরিভেদে আলাদা. MSVC আর নতুন GCC-র libstdc++-এ এটি আছে, কিন্তু কিছু লাইব্রেরি আর পুরোনো compiler-এ এখনো অসম্পূর্ণ, আর অনলাইন compiler-এ time zone ডেটাবেস না-ও থাকতে পারে। ব্যবহারের আগে নিজের টুলচেইনে পরীক্ষা করুন।

৬ · <random> — rand() ছেড়ে engine আর distribution

C থেকে আসা সবচেয়ে পরিচিত এলোমেলো সংখ্যার কোড এমন: srand(time(nullptr)); তারপর rand() % 6 + 1. এতে কয়েকটি সমস্যা একসাথে:

  • মান অনির্দিষ্ট: standard rand()-এর অ্যালগরিদম বলে দেয় না। কিছু বাস্তবায়নে RAND_MAX মাত্র 32767 (যেমন MSVC), আর পুরোনো কিছু বাস্তবায়নে নিচের bit-গুলোর ধারা খুব নিয়মিত ছিল।
  • ভাগশেষের পক্ষপাত: RAND_MAX 32767 হলে মোট ৩২৭৬৮টি সম্ভাব্য মান। % 10 নিলে ০ থেকে ৭ আসে ৩২৭৭ বার করে, কিন্তু ৮ আর ৯ আসে ৩২৭৬ বার। পার্থক্য ছোট, কিন্তু সীমা যত বড়, পক্ষপাত তত বেশি। একে বলে modulo bias.
  • লুকানো global অবস্থা: পুরো প্রোগ্রামে একটিই ধারা। একটি লাইব্রেরি গোপনে srand ডাকলে আপনার ধারা বদলে যায়, আর একাধিক thread থেকে ডাকলে নিরাপদ হওয়ার নিশ্চয়তা নেই।
  • দুর্বল seed: time(nullptr) সেকেন্ডে বদলায়। একই সেকেন্ডে চালু হওয়া দুটি প্রোগ্রাম হুবহু একই “এলোমেলো” ধারা পায়।

C কোর্সের rand() অংশে এগুলো বিস্তারিত আছে। C++11-এর <random> কাজটিকে দুই ভাগে ভাগ করেছে:

Engine — কাঁচা bit-এর কারখানা

একটি random engine নিজের ভেতরের অবস্থা (state) থেকে একের পর এক সমান-সম্ভাব্য পূর্ণসংখ্যা বানায়। সবচেয়ে প্রচলিত std::mt19937 (৩২-বিট) আর std::mt19937_64. একই seed দিলে একই ধারা।

কোথা থেকে এলোমেলোতা আসে

Distribution — আকার দেওয়ার যন্ত্র

একটি distribution engine-এর কাঁচা সংখ্যা নিয়ে চাওয়া সীমা আর আকারে আনে, পক্ষপাত ছাড়া: uniform_int_distribution, uniform_real_distribution, normal_distribution, bernoulli_distribution.

কোন আকারে এলোমেলোতা চাই

engine-কে শুরুর অবস্থা দেয় একটি seed. আসল এলোমেলোতার জন্য seed নেওয়া হয় std::random_device থেকে, যা সাধারণত অপারেটিং সিস্টেমের এলোমেলোতার উৎস ব্যবহার করে। পুরো পাইপলাইনটি ধাপে ধাপে দেখুন:

  1. seed2026শুরুর বীজ
  2. enginemt19937৬২৪ শব্দের state
  3. কাঁচা bit942082305৩২-বিট পূর্ণসংখ্যা
  4. distribution[1, 6]পক্ষপাতহীন আকার
  5. মান1..6ছক্কার দান
seed বেছে নেওয়াদুই রকম প্রয়োজন
std::mt19937 gen{2026};                    // টেস্ট: প্রতিবার একই ধারা
std::mt19937 gen{std::random_device{}()};  // আসল ব্যবহার: প্রতিবার আলাদা
engine-এর ভেতরMersenne Twister
state: ৬২৪টি ৩২-বিট সংখ্যা (প্রায় ২.৫ KB)
seed 2026 থেকে state ভরে নেওয়ার নিয়ম standard-এ লেখা
=> সব compiler-এ হুবহু একই state
gen()কাঁচা আউটপুট — standard-নির্ধারিত
প্রথম তিনটি:  942082305  3292560774  1773871898
সীমা: 0 থেকে 4294967295
GCC, Clang, MSVC — সবখানে এই একই তিনটি সংখ্যা
uniform_int_distribution{1, 6}আকার দেওয়া
std::uniform_int_distribution die{1, 6};   // দুই প্রান্তসহ [1, 6]
int roll = die(gen);   // দরকার হলে gen() একাধিকবার ডাকে,
                       // যাতে ১ থেকে ৬ সমান সম্ভাবনায় আসে
অ্যালগরিদম: লাইব্রেরি নিজে বেছে নেয়
ফলপুনরাবৃত্তিযোগ্য — কিন্তু কোথায়?
একই seed + একই লাইব্রেরি  => প্রতিবার একই দান
একই seed + অন্য লাইব্রেরি  => দান আলাদা হতে পারে
(engine একই, distribution-এর অ্যালগরিদম আলাদা)
  1. seed — সব শুরু এখানে। টেস্ট বা বাগ খোঁজার সময় একটি স্থির seed দিন, যাতে প্রতিবার একই ঘটনা ঘটে। আসল ব্যবহারে seed নিন random_device থেকে। seed-টি লগে লিখে রাখলে পরে যেকোনো সমস্যার ঘটনা হুবহু আবার চালানো যায়।
  2. engine-এর অবস্থা। mt19937-এর ভেতরে ৬২৪টি সংখ্যার একটি অবস্থা থাকে। seed থেকে এই অবস্থা কীভাবে ভরা হবে, আর প্রতিটি সংখ্যা কীভাবে বের হবে — দুটিই standard হুবহু লিখে দিয়েছে। তাই engine নিজে সম্পূর্ণ নির্ধারিত (deterministic).
  3. কাঁচা সংখ্যা সব জায়গায় এক। seed 2026 দিলে প্রথম আউটপুট 942082305 — যেকোনো সঠিক C++ লাইব্রেরিতে। standard একটি পরীক্ষাও দিয়ে রেখেছে: ডিফল্ট seed-এ তৈরি mt19937-এর ১০০০০তম আউটপুট হতে হবে 4123659995.
  4. distribution আকার দেয়। কাঁচা ৩২-বিট সংখ্যাকে ১ থেকে ৬-এ আনতে % 6 নয় — distribution পক্ষপাত দূর করে, দরকার হলে কিছু কাঁচা মান বাদ দিয়ে আবার চায়। খেয়াল করুন, uniform_int_distribution-এর সীমা দুই প্রান্তসহ: {1, 6} মানে ১ আর ৬ দুটিই আসতে পারে।
  5. পুনরাবৃত্তির সীমা। standard distribution-এর ফল কোন পদ্ধতিতে বের হবে, তা বলে দেয়নি — শুধু ফলাফলের সম্ভাবনার নিয়ম বলেছে। তাই GCC-তে যে দান 4, MSVC-তে সেটি 2 হতে পারে। টেস্টে distribution-এর ফল “প্রত্যাশিত মান” হিসেবে লিখে রাখলে অন্য compiler-এ টেস্ট ভাঙবে। সব প্ল্যাটফর্মে হুবহু এক ফল লাগলে কাঁচা engine-এর আউটপুটের ওপর নিজের ছোট রূপান্তর লিখুন, বা একটি নির্দিষ্ট লাইব্রেরি ব্যবহার করুন।

এবার বাস্তবের একটি কাজ: প্রতিদিন কুইজের ২০টি প্রশ্ন এলোমেলো ক্রমে দেওয়া। একটি engine একবার বানিয়ে বারবার ব্যবহার করুন — প্রতিবার ডাকার সময় নতুন engine বানানো ধীর, আর ফলও খারাপ হয়:

#include <algorithm>
#include <numeric>
#include <random>
#include <vector>

std::vector<int> quiz_order(std::mt19937& gen) {   // engine বাইরে থেকে আসে
    std::vector<int> q(20);
    std::iota(q.begin(), q.end(), 1);              // 1, 2, ..., 20
    std::ranges::shuffle(q, gen);
    return q;
}

// টেস্টে:   std::mt19937 gen{2026};                    — প্রতিবার একই ক্রম
// আসল অ্যাপে: std::mt19937 gen{std::random_device{}()}; — প্রতিবার নতুন ক্রম

এখানেও একই সতর্কতা খাটে: shuffle-এর ভেতরের পদ্ধতিও লাইব্রেরি ঠিক করে, তাই একই seed-এ GCC আর MSVC ভিন্ন ক্রম দিতে পারে। একই লাইব্রেরিতে কিন্তু প্রতিবার একই ক্রম — বাগ খোঁজার জন্য সেটাই যথেষ্ট। আর একটি সূক্ষ্ম বিষয়: random_device{}() একটিমাত্র ৩২-বিট সংখ্যা দেয়, অথচ mt19937-এর অবস্থা প্রায় ২০,০০০ বিটের। তাই এভাবে seed দিলে সম্ভাব্য ধারা মাত্র প্রায় ৪৩০ কোটি। কুইজ বা গেমের জন্য যথেষ্ট; বৈজ্ঞানিক সিমুলেশনে সাধারণত std::seed_seq দিয়ে আরও বেশি seed-ডেটা দেওয়া হয়।

পাসওয়ার্ড, OTP, session token বা লটারির আসল ড্র — এগুলোর জন্য mt19937 নয়। Mersenne Twister দ্রুত আর পরিসংখ্যানে ভালো, কিন্তু অনুমানযোগ্য: পরপর ৬২৪টি আউটপুট দেখলেই এর পুরো অবস্থা হিসাব করে ফেলা যায়, আর তারপর ভবিষ্যতের প্রতিটি সংখ্যা জানা যায়। কেউ আপনার অ্যাপের কয়েকশো OTP দেখে পরেরটি বলে দিলে ব্যবহারকারীর অ্যাকাউন্ট আর নিরাপদ নয়। নিরাপত্তার কাজে লাগে CSPRNG — অপারেটিং সিস্টেমের নিরাপদ উৎস বা libsodium-এর মতো পরীক্ষিত লাইব্রেরি। random_device অনেক প্ল্যাটফর্মে নিরাপদ উৎস থেকেই আসে, কিন্তু standard তা নিশ্চিত করে না — পুরোনো কিছু MinGW সংস্করণে এটি প্রতিবার একই ধারা দিত।

৭ · ছোট কিন্তু কাজের — format, numeric, bit আর utility

format আর print — একবার মনে করিয়ে দেওয়া

পর্ব ৭-এ আপনি std::format (C++20) আর std::println (C++23) দেখেছেন: {} জায়গায় মান বসে, টাইপ যাচাই হয় compile-এর সময়। chrono-র টাইপগুলোর জন্য এদের নিজস্ব ফরম্যাট-চিহ্ন আছে, যা C-এর strftime-এর মতো দেখতে:

using namespace std::chrono;
year_month_day d{2026y / October / 17};
std::println("{}", d);                 // 2026-10-17   (ISO ফরম্যাট, ডিফল্ট)
std::println("{:%d/%m/%Y}", d);        // 17/10/2026
std::println("{:%d %B %Y}", d);        // 17 October 2026
std::println("{}", hh_mm_ss{4537s});   // 01:15:37
std::println("{}", 3min + 15s);        // 195s

লগ বা ফাইলে তারিখ রাখার সময় ডিফল্ট ISO ফরম্যাট (2026-10-17) সবচেয়ে ভালো — অক্ষর অনুযায়ী সাজালেই তারিখ অনুযায়ী সাজানো হয়, আর কোনো দেশে এর অর্থ নিয়ে বিভ্রান্তি নেই। (10/11/2026 — ১০ নভেম্বর না ১১ অক্টোবর?) ব্যবহারকারীকে দেখানোর সময় তাঁর পরিচিত ফরম্যাট দিন।

<numeric>, <numbers> আর <cmath>

std::gcd(12, 18);                          // 6   (C++17, <numeric>)
std::lcm(4, 6);                            // 12
std::midpoint(2147483600, 2147483640);     // 2147483620 — overflow হয় না
std::numbers::pi;                          // 3.141592653589793 (C++20, <numbers>)
std::hypot(3.0, 4.0);                      // 5   (<cmath>)
std::lerp(10.0, 20.0, 0.25);               // 12.5 (C++20)

প্রতিটির পেছনে একটি গল্প আছে। std::gcd আর std::lcm — কনটেস্টে এখনো অনেকে নিজের Euclid লুপ লেখেন বা GCC-র অ-standard __gcd ব্যবহার করেন; C++17 থেকে এর দরকার নেই। std::midpoint — (a + b) / 2-এ দুটি বড় int যোগ করলেই overflow, আর signed overflow মানে undefined behavior. binary search-এ এই বাগ বছরের পর বছর বিখ্যাত লাইব্রেরিগুলোতে লুকিয়ে ছিল। std::numbers::pi — M_PI আসলে C++ standard-এর অংশ নয়, কিছু প্ল্যাটফর্মে বিশেষ macro ছাড়া পাওয়াই যায় না; C++20-এ pi, e, sqrt2 সহ ধ্রুবকগুলো এসেছে টাইপসহ।

<bit> — bit গোনা আর ২-এর ঘাত

C++20-এর <bit> header সেইসব কাজ দেয়, যা আগে compiler-এর বিশেষ ফাংশন (__builtin_popcount) বা হাতে লেখা কৌশলে করতে হতো। সবগুলো শুধু unsigned টাইপে কাজ করে:

std::popcount(13u);         // 3    — 1101-এ তিনটি 1
std::bit_ceil(100u);        // 128  — 100-এর সমান বা বড় সবচেয়ে ছোট ২-এর ঘাত
std::has_single_bit(64u);   // true — 64 কি ২-এর ঘাত?
std::countr_zero(40u);      // 3    — 101000-এর ডানে তিনটি 0

std::popcount কনটেস্টে bitmask DP-তে প্রতিদিনের যন্ত্র; bit_ceil লাগে hash table বা ring buffer-এর মাপ ২-এর ঘাতে আনতে। আধুনিক CPU-তে এগুলোর বেশিরভাগ একটিমাত্র মেশিন-নির্দেশে চলে, আর compiler সেটি নিজে বেছে নেয়। C++23-এ এসেছে std::byteswap, যা নেটওয়ার্ক বা ফাইল-ফরম্যাটের byte-ক্রম উল্টাতে কাজে লাগে।

utility-র টুকিটাকি

কয়েকটি ছোট যন্ত্র, যা আগের পর্বগুলোতে এসেছে, এক জায়গায়:

std::pair p{"Rahim", 87};              // দুটি মান একসাথে (পর্ব ১২)
std::tuple t{"Dhaka", 2026, 4.5};      // যতগুলো খুশি
auto [name, marks] = p;                // structured binding

std::swap(a, b);                       // দুটির মান অদলবদল
int old = std::exchange(x, 0);         // x = 0, আর পুরোনো মান ফেরত (পর্ব ২২)
int pct = std::clamp(score, 0, 100);   // 0-এর নিচে 0, 100-এর ওপরে 100
auto [lo, hi] = std::minmax({4, 9, 2}); // lo = 2, hi = 9

এদের প্রত্যেকটি এক লাইনের কাজ, কিন্তু নাম দিয়ে উদ্দেশ্য বলে দেয়। std::clamp লেখা থাকলে পাঠক এক নজরে বোঝেন “সীমার ভেতরে আনা হচ্ছে”; দুটি nested if দেখে সেটা বুঝতে সময় লাগে, আর < আর <=-এর ভুলও লুকিয়ে থাকতে পারে। ছোট যন্ত্র চেনার লাভ এটাই — কোড ছোট হয়, আর পড়া সহজ হয়।

৮ · লেখা থেকে সংখ্যা: from_chars — আর filesystem-এর এক ঝলক

ধরুন একটি CSV ফাইলের একটি লাইন 87,92,8x,75 — কেউ ভুল করে একটি নম্বরে একটি অক্ষর টাইপ করে ফেলেছেন। প্রচলিত উপায়গুলো এখানে কী করে?

উপায়"8x" দিলেসমস্যা
atoi8, কোনো সংকেত নেইভুল ধরার কোনো উপায় নেই; "abc" দিলে 0 — আসল 0-এর সাথে পার্থক্য নেই
std::stoi8, exception নেইশুধু শুরুর অংশ পড়ে; একদম না পারলে exception; শুরুর ফাঁকা জায়গা নীরবে বাদ দেয়
strtol / strtod8, শেষ-বিন্দু জানায়ঠিকমতো যাচাই করা জটিল; দশমিকের ক্ষেত্রে locale মানে
std::from_chars8, কিন্তু থামার জায়গা x জানায়নিজে যাচাই করতে হয় — তবে যাচাই সরল আর নির্ভরযোগ্য

C++17-এর std::from_chars (header <charconv>) চারটি জিনিস করে না: exception ছোড়ে না, মেমরি নেয় না, locale দেখে না, আর শুরুর ফাঁকা জায়গা বাদ দেয় না। বদলে ফেরত দেয় দুটি জিনিস — কোথায় থামল (ptr) আর কোনো ভুল হলো কি না (ec, একটি std::errc). locale না মানার গুরুত্ব বড়: strtod জার্মান locale-এ চললে 3.14-কে পড়ে শুধু 3, কারণ সেখানে দশমিক চিহ্ন কমা। একটি সার্ভার কোন দেশের সেটিংসে চলছে, তার ওপর ফাইল-পড়ার ফল নির্ভর করা উচিত নয়। উল্টো কাজের জন্য আছে std::to_chars, যা দশমিক সংখ্যাকে সবচেয়ে ছোট এমন লেখায় আনে, যা পড়ে আবার হুবহু একই সংখ্যা পাওয়া যায়।

এবার CSV লাইনটি নির্ভরযোগ্যভাবে পড়ুন, আর সাথে mt19937-এর সেই standard-নির্ধারিত সংখ্যাটি নিজের চোখে দেখুন:

#include <charconv>
#include <print>
#include <random>
#include <ranges>
#include <string_view>

int main() {
    std::mt19937 gen;                              // default seed 5489
    gen.discard(9999);                             // skip 9999 values
    std::println("10000th raw value: {}", gen());  // fixed by the standard

    std::string_view line = "87,92,8x,75";
    int sum = 0;
    for (auto part : line | std::views::split(',')) {
        std::string_view f(part.begin(), part.end());
        int v = 0;
        auto [end, ec] = std::from_chars(f.data(), f.data() + f.size(), v);
        if (ec == std::errc{} && end == f.data() + f.size()) sum += v;
        else std::println("skip bad field '{}'", f);
    }
    std::println("sum = {}", sum);
}

আউটপুট তিন লাইন: 10000th raw value: 4123659995, skip bad field '8x', আর sum = 254 (৮৭ + ৯২ + ৭৫)। প্রথম লাইনটি যেকোনো সঠিক C++ লাইব্রেরিতে একই — standard নিজেই এই সংখ্যাটি লিখে রেখেছে, যাতে বাস্তবায়নগুলো নিজেদের যাচাই করতে পারে। CSV অংশে মূল কৌশল দুটি শর্ত: ec == std::errc{} মানে অন্তত কিছু একটা পড়া গেছে, আর end লেখার শেষে পৌঁছেছে মানে পুরো ঘরটিই সংখ্যা। "8x"-এ প্রথম শর্ত সত্য (8 পড়া গেছে), কিন্তু দ্বিতীয়টি মিথ্যা (থেমেছে x-এ) — তাই বাদ। শুধু ec দেখলে আপনিও atoi-এর মতো 8 নিয়ে নিতেন। ঘর ভাগ করেছে পর্ব ৩৫-এর views::split.

filesystem — এক ঝলক

C++17-এর std::filesystem ফাইল আর ফোল্ডারের পথ নিয়ে কাজ করে — সব অপারেটিং সিস্টেমে একই কোডে। এর কেন্দ্রে path টাইপ, যা Windows-এর \ আর Linux-এর / দুটিই সামলায়। শুধু স্বাদ নেওয়ার জন্য:

#include <filesystem>
namespace fs = std::filesystem;

fs::path p = "results/2026/marks.csv";
p.filename();      // "marks.csv"
p.extension();     // ".csv"
p.parent_path();   // "results/2026"

if (fs::exists(p)) { /* ফাইলটি আছে */ }
for (const auto& e : fs::directory_iterator{"results"})   // ফোল্ডারের তালিকা
    std::println("{}", e.path().string());

পথ জোড়া দেওয়া (dir / "file.txt"), ফাইল কপি ও মোছা, ফোল্ডার তৈরি, ভুল সামলানো, আর ফাইল stream দিয়ে ফাইল পড়া-লেখা — সব পর্ব ৪৪-এ বিস্তারিত। আজ শুধু মানচিত্রে জায়গাটা চিনে রাখুন: ফাইলের পথ নিয়ে string কাটাকাটি নয়, fs::path.

পরীক্ষা ও ইন্টারভিউতে
  • steady_clock আর system_clock-এর পার্থক্য কী? — steady_clock কখনো পেছনে যায় না, তাই সময়কাল মাপার জন্য; system_clock দেয়ালঘড়ি, বদলাতে পারে, তারিখ-সময় দেখানোর জন্য।
  • duration_cast<seconds>(1500ms) কত? — 1s; duration_cast শূন্যের দিকে কেটে ফেলে। নিকটতম মান চাইলে round.
  • rand() % n-এর সমস্যা কী? — RAND_MAX + 1 যদি n দিয়ে বিভাজ্য না হয়, ছোট মানগুলো বেশি আসে (modulo bias); তাছাড়া rand()-এর মান অনির্দিষ্ট। সমাধান: engine + uniform_int_distribution.
  • একই seed দিলে কি সব compiler-এ একই এলোমেলো সংখ্যা আসে? — engine-এর কাঁচা আউটপুট আসে (standard-নির্ধারিত), কিন্তু distribution-এর ফল লাইব্রেরিভেদে আলাদা হতে পারে।
  • from_chars কেন stoi-এর চেয়ে ভালো? — exception ছোড়ে না, locale মানে না, মেমরি নেয় না, আর কোথায় থামল তা জানায়, ফলে "8x"-এর মতো আংশিক সংখ্যা ধরা যায়।

৯ · AI-এর যুগে — সময়, তারিখ আর random-এর কোডে AI-এর ভুল

সময় আর এলোমেলো সংখ্যার কোডে AI সবচেয়ে বেশি “পুরোনো অভ্যাস” দেখায়। কারণ ইন্টারনেটে বিশ বছরের পুরোনো উদাহরণে ভরা — srand(time(0)), clock() দিয়ে benchmark, হাতে লেখা দিন গোনা। এসব কোড compile হয়, ছোট পরীক্ষায় ঠিকঠাক চলে, আর ভুল ফল দেয় শুধু বিশেষ দিনে — অধিবর্ষে, মাসের ৩১ তারিখে, রাত ১২টার পরে, বা ঘড়ি মেলানোর মুহূর্তে। সাধারণ ভুলগুলো:

AI যা লেখেসমস্যাযা হওয়া উচিত
rand() % n, srand(time(nullptr))পক্ষপাত, অনির্দিষ্ট মান, একই সেকেন্ডে একই ধারাstd::mt19937 + uniform_int_distribution
প্রতি ডাকে নতুন engine ও random_deviceধীর; কিছু প্ল্যাটফর্মে বারবার একই সংখ্যাengine একবার বানিয়ে reference দিয়ে পাঠান
system_clock বা high_resolution_clock দিয়ে benchmarkঘড়ি বদলালে ভুল, এমনকি ঋণাত্মক সময়steady_clock
days = months * 30, হাতে অধিবর্ষের নিয়মকয়েক দিনের ভুল; ২১০০ সালের মতো ব্যতিক্রম বাদyear_month_day, sys_days, ok()
UTC তারিখকেই স্থানীয় “আজ” ধরাঢাকায় রাত ১২টা–সকাল ৬টায় ভুল দিনzoned_time{"Asia/Dhaka", ...}
atoi, বা যাচাই ছাড়া stoi"8x" হয়ে যায় 8; "abc" হয়ে যায় 0from_chars + ec ও ptr যাচাই
OTP বা পাসওয়ার্ডে mt19937অনুমানযোগ্য — নিরাপত্তা ভাঙেCSPRNG (OS-এর উৎস বা libsodium)

AI-কে এমন কোড লিখতে বলার সময় কোন লাইব্রেরি আর কোন নিয়ম চান, স্পষ্ট করে দিন। নিচের prompt-টি কপি করে নিজের প্রয়োজনে বদলে নিন:

আমি C++23 শিখছি (g++, -Wall -Wextra). পরীক্ষার প্রস্তুতির একটি ছোট কনসোল প্রোগ্রাম লেখো: ১. পরীক্ষার তারিখ 2026-12-10; ঢাকার আজকের তারিখ থেকে কত দিন বাকি দেখাবে। std::chrono-র year_month_day আর sys_days ব্যবহার করবে; কোথাও "মাস = ৩০ দিন" ধরবে না। আজকের তারিখ একটি ফাংশন-প্যারামিটার হিসেবে নেবে, যাতে টেস্টে স্থির তারিখ দেওয়া যায়। ২. ২০টি প্রশ্ন এলোমেলো ক্রমে দেবে: std::mt19937 একবার বানাবে, seed প্যারামিটার হিসেবে নেবে (টেস্টে স্থির, আসলে random_device); rand(), srand(), time() একেবারেই নয়। ৩. প্রতিটি উত্তরে কত সময় লাগল মাপবে শুধু steady_clock দিয়ে। ৪. "87,92,8x,75" এমন লাইন থেকে নম্বর পড়বে std::from_chars দিয়ে; ec আর থামার জায়গা দুটিই যাচাই করবে, ভুল ঘর জানিয়ে বাদ দেবে। কোডের পরে বলো: কোন ফল সব compiler-এ একই হবে আর কোনটি লাইব্রেরিভেদে আলাদা হতে পারে, আর এই কোড কেন OTP বানানোর জন্য নিরাপদ নয়।

AI-এর লেখা সময়/random কোড যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১chrono C++11-এ এল শুধু duration আর clock নিয়ে; ক্যালেন্ডার আর time zone আসতে লাগল আরও প্রায় এক দশক। Howard Hinnant-এর এই যাত্রা থেকে লাইব্রেরি-নকশা সম্পর্কে কী শেখা যায়?

C++11-এর আগে C++-এ সময়ের কোনো টাইপ-নিরাপদ ব্যবস্থা ছিল না — ছিল C-এর time_t, clock() আর অপারেটিং সিস্টেমের নিজস্ব API, যেখানে সময় মানে একটি সাধারণ সংখ্যা। ২০০৮ সালের দিকে Howard Hinnant (Walter E. Brown আর অন্য কয়েকজনের সাথে) chrono-র প্রস্তাব দেন, যা C++11-এ গৃহীত হয়। নকশার মূল সিদ্ধান্তগুলো ছিল সাহসী: একক থাকবে টাইপে (std::ratio দিয়ে, যেমন হাজার ভাগের এক), এককের সব রূপান্তর হবে compile-এর সময়, ক্ষতিকর রূপান্তর নিজে থেকে হবে না, আর এই সবকিছুর পরও একটি duration হবে শুধু একটি পূর্ণসংখ্যার সমান মাপের — হাতে লেখা long long-এর চেয়ে একটুও ধীর নয়। অর্থাৎ নিরাপত্তা আর গতির মধ্যে আপস নয়, দুটিই।

কিন্তু C++11-এর chrono ইচ্ছা করেই ক্যালেন্ডার বাদ রেখেছিল। তারিখ কঠিন: অধিবর্ষ, মাসের ভিন্ন দৈর্ঘ্য, “৩১ জানুয়ারি + এক মাস” কী হবে তার একাধিক যুক্তিসংগত উত্তর, আর time zone-এর নিয়ম, যা সরকারগুলো যখন-তখন বদলায়। তাড়াহুড়ো করে standard-এ কিছু ঢুকালে সেটি আর কখনো বদলানো যায় না। তাই Hinnant পরের বছরগুলোতে chrono-র ওপর একটি মুক্ত-উৎস “date” লাইব্রেরি লেখেন, GitHub-এ প্রকাশ করেন, আর বাস্তব প্রজেক্টে কয়েক বছর ধরে ব্যবহার হতে দেন। তিনি দিন গোনা থেকে তারিখ আর তারিখ থেকে দিন গোনার দ্রুত অ্যালগরিদমও বিস্তারিত লিখে প্রকাশ করেন। ব্যবহারকারীদের মতামত থেকে নকশা পাকা হয় — যেমন ঘরভিত্তিক year_month_day আর সংখ্যাভিত্তিক sys_days আলাদা রাখা, অবৈধ তারিখ নিজে থেকে না শুধরে ok() দিয়ে জানানো। শেষে এটি প্রস্তাব হিসেবে কমিটিতে যায়, আর ২০১৮ সালে গৃহীত হয়ে C++20-এর অংশ হয়। time zone অংশ IANA-র ডেটাবেসের ওপর দাঁড়ানো, যাতে নিয়ম বদলালে শুধু ডেটা হালনাগাদ করলেই চলে।

এই পথটি C++-এর অনেক সফল অংশের সাধারণ গল্প: আগে বাস্তবে ব্যবহার (Boost বা মুক্ত-উৎস লাইব্রেরি), তারপর standard. {fmt} থেকে std::format, Boost.Filesystem থেকে std::filesystem, range-v3 থেকে ranges — একই ধারা। উল্টো দিকে, যে অংশগুলো বাস্তব অভিজ্ঞতা ছাড়া তাড়াহুড়ো করে ঢুকেছে, সেগুলোর অনেককে পরে ঝামেলা পোহাতে হয়েছে।

মূল উপলব্ধি: ভালো লাইব্রেরি তৈরি হয় ধৈর্যে — ছোট, নিখুঁত ভিত্তি (duration) আগে, জটিল অংশ (ক্যালেন্ডার) বাস্তবে পরীক্ষিত হওয়ার পরে। আর নকশার সবচেয়ে বড় শক্তি হলো ভুলকে compile error বানানো।

প্র ০২<random> engine আর distribution-কে আলাদা করল কেন, যখন অন্য অনেক ভাষায় শুধু random.randint(1, 6) লিখলেই হয়? এই বিভাজনের লাভ আর দাম কী?

<random> C++11-এ এসেছে মূলত Boost.Random আর TR1-এর অভিজ্ঞতা থেকে। এর নকশাকারেরা এলোমেলো সংখ্যার কাজকে দুটি স্বাধীন প্রশ্নে ভাগ করেন। প্রথম প্রশ্ন: “এলোমেলো bit কোথা থেকে আসবে?” — দ্রুত দরকার, না দীর্ঘ পর্যায়কাল (period), না খুব ছোট মেমরি? দ্বিতীয় প্রশ্ন: “কোন আকারে চাই?” — ছক্কার মতো সমান সম্ভাবনা, উচ্চতার মতো normal বণ্টন, না মুদ্রা টসের মতো হ্যাঁ/না? এই দুটি প্রশ্নের উত্তর একে অপরের ওপর নির্ভর করে না। তাই engine আর distribution আলাদা হলে যেকোনো engine-এর সাথে যেকোনো distribution জোড়া লাগানো যায়। ৫টি engine আর ২০টি distribution মানে ১০০টি আলাদা কোড নয়, ২৫টি — ঠিক STL-এর container আর algorithm আলাদা রাখার মতো চিন্তা (পর্ব ৩৩)।

দ্বিতীয় বড় লাভ হলো নিয়ন্ত্রণ। Python-এর random module-এ পেছনে একটি global engine লুকানো, ঠিক C-এর rand()-এর মতো। C++-এ engine একটি সাধারণ object: আপনি ঠিক করেন কয়টি থাকবে, কে কোনটি ব্যবহার করবে, কোন seed পাবে। একটি সিমুলেশনে প্রতিটি thread নিজের engine পেতে পারে; একটি গেমে মানচিত্র তৈরির engine আর শত্রুর আচরণের engine আলাদা রাখা যায়, যাতে একটি বদলালে অন্যটির ধারা না বদলায়।

দাম কী? প্রথমত, শুরু করতে বেশি কোড লাগে — নতুন শিক্ষার্থীর কাছে তিন লাইন বিরক্তিকর লাগে। দ্বিতীয়ত, এবং আরও গুরুত্বপূর্ণ, standard engine-গুলোর অ্যালগরিদম হুবহু নির্দিষ্ট করেছে, কিন্তু distribution-এর অ্যালগরিদম নয় — শুধু ফলাফলের সম্ভাবনার নিয়ম। উদ্দেশ্য ছিল বাস্তবায়নকারীদের ভালো অ্যালগরিদম বেছে নেওয়ার স্বাধীনতা দেওয়া (যেমন normal বণ্টনের জন্য একাধিক পরিচিত পদ্ধতি আছে)। ফল হলো, একই seed-এ GCC আর MSVC ভিন্ন দান দেয়, আর যাঁদের সব প্ল্যাটফর্মে হুবহু এক ফল দরকার — যেমন নেটওয়ার্কে খেলা গেম, যেখানে দুই কম্পিউটারকে একই “এলোমেলো” ঘটনা দেখতে হয় — তাঁরা নিজের distribution লেখেন। seeding-এর নকশা নিয়েও সমালোচনা আছে: PCG engine-এর লেখক Melissa O'Neill প্রমুখ দেখিয়েছেন, seed_seq দিয়ে mt19937-এর বিশাল অবস্থা ঠিকমতো ভরানো সহজ নয়।

মূল উপলব্ধি: দায়িত্ব আলাদা করলে নমনীয়তা আর নিয়ন্ত্রণ বাড়ে, কিন্তু কোন অংশ “নির্দিষ্ট” আর কোন অংশ “বাস্তবায়নের স্বাধীনতা” — সেই সীমারেখা ব্যবহারকারীকে জানতে হয়। পুনরাবৃত্তিযোগ্যতা চাইলে ঠিক কোন স্তরে তা নিশ্চিত, সেটা বুঝে নকশা করুন।

প্র ০৩Mersenne Twister প্রায় তিন দশক ধরে বিশ্বের সবচেয়ে জনপ্রিয় এলোমেলো-সংখ্যার engine-গুলোর একটি। এর শক্তি কোথায়, দুর্বলতা কোথায় — আর আজ বিকল্প কী?

Mersenne Twister প্রকাশ করেন দুই জাপানি গণিতবিদ, Makoto Matsumoto আর Takuji Nishimura, ১৯৯৭ সালে (গবেষণাপত্রটি ১৯৯৮ সালে জার্নালে ছাপা হয়)। নামটি এসেছে এর পর্যায়কাল থেকে: 219937 − 1, যা একটি Mersenne মৌলিক সংখ্যা — অর্থাৎ ধারাটি নিজেকে পুনরাবৃত্তি করার আগে এত বেশি সংখ্যা দেয় যে মহাবিশ্বের বয়সেও তা শেষ হবে না। এটি ৬২৩ মাত্রা পর্যন্ত সমভাবে ছড়ানো (equidistributed), অর্থাৎ পরপর আসা ৩২-বিট সংখ্যার দল দিয়ে বানানো বিন্দুগুলো বহু মাত্রার জায়গায় সমানভাবে ছড়িয়ে থাকে। তার আগের অনেক জনপ্রিয় generator — বিশেষ করে সরল linear congruential generator — এ ধরনের পরীক্ষায় খারাপভাবে ব্যর্থ হতো। ফলে MT দ্রুত মান-নির্ধারক হয়ে ওঠে: Python-এর random module, R-এর ডিফল্ট generator, আরও অনেক ভাষা ও টুল এটি গ্রহণ করে, আর C++11-এ এটি std::mt19937 নামে আসে।

দুর্বলতাও কম নয়। প্রথমত, অবস্থা বড় — প্রায় ২.৫ KB, অথচ আধুনিক কিছু engine মাত্র ১৬ বা ৩২ বাইটে সমান বা ভালো মান দেয়। লাখ লাখ ছোট সিমুলেশন একসাথে চালাতে এই মাপ চোখে পড়ে। দ্বিতীয়ত, এটি সম্পূর্ণ রৈখিক (linear) গণিতে তৈরি; TestU01-এর মতো কঠোর পরীক্ষার সংকলনে রৈখিকতা ধরার কিছু পরীক্ষায় এটি ব্যর্থ হয়। তৃতীয়ত, অবস্থায় যদি প্রায় সব bit শূন্য হয়, ধারাটি স্বাভাবিক “এলোমেলো” চেহারায় ফিরতে অনেক সময় নেয়। চতুর্থত, এবং সবচেয়ে গুরুত্বপূর্ণ, এটি নিরাপত্তার জন্য নয়: আউটপুট থেকে অবস্থা উল্টো হিসাব করা যায়, তাই পরপর ৬২৪টি আউটপুট দেখলে ভবিষ্যতের সব সংখ্যা বলে দেওয়া যায়। আর একটিমাত্র ৩২-বিট seed দিলে বিশাল অবস্থার মাত্র প্রায় ৪৩০ কোটি শুরুর বিন্দু ব্যবহার হয়।

বিকল্প হিসেবে আজ জনপ্রিয় PCG পরিবার (Melissa O'Neill, ২০১৪) আর xoshiro/xoroshiro পরিবার (David Blackman ও Sebastiano Vigna) — ছোট অবস্থা, দ্রুত, আর কঠোর পরীক্ষায় ভালো ফল। Matsumoto-র দলও SFMT-এর মতো দ্রুততর সংস্করণ বানিয়েছে। কিন্তু এগুলোর কোনোটিই এখনো C++ standard-এ নেই, তাই portable কোডে mt19937 এখনো সবচেয়ে সহজ পছন্দ।

মূল উপলব্ধি: “ভালো এলোমেলো” একটি সংখ্যা নয়, কয়েকটি মাপকাঠি — পরিসংখ্যানের মান, গতি, মেমরি, আর অনুমান-অযোগ্যতা। mt19937 প্রথম তিনটিতে যথেষ্ট ভালো, চতুর্থটিতে একেবারেই নয়। কাজ বুঝে engine বেছে নিন।

প্র ০৪C++-এ তো আগেই atoi, strtol, sprintf, stringstream, std::stoi, std::to_string ছিল। তবু C++17-এ আবার to_chars/from_chars যোগ করতে হলো কেন?

কারণ পুরোনো প্রতিটি উপায় এমন কিছু করত, যা উচ্চ-গতির বা নির্ভরযোগ্য কোডে চাওয়া হয় না। sprintf আর strtod চলার সময়ের locale মানে: প্রোগ্রাম যদি এমন locale-এ চলে যেখানে দশমিক চিহ্ন কমা, তাহলে 3.14 লিখতে বা পড়তে গিয়ে ফল বদলে যায়। একটি JSON ফাইল বা নেটওয়ার্ক প্রোটোকলে দশমিক চিহ্ন সবসময় ডট — সেখানে locale-নির্ভরতা মানে লুকানো বাগ, যা শুধু বিশেষ দেশের ব্যবহারকারীর কম্পিউটারে দেখা দেয়। locale খোঁজার কাজটিও সময় নেয়। stringstream আরও ভারী: object তৈরি, locale, buffer, virtual ডাক। std::to_string প্রতিবার নতুন string-এর জন্য মেমরি নেয়। std::stoi ভুলে exception ছোড়ে, অথচ লাখ লাখ লাইনের ফাইল পড়ায় ভুল ঘর খুবই স্বাভাবিক ঘটনা, ব্যতিক্রম নয়। আর atoi ভুল ধরতেই পারে না।

Jens Maurer-এর প্রস্তাব (“elementary string conversions”) তাই খুব সরল একটি চুক্তি দেয়: আপনার দেওয়া buffer-এ লিখবে বা পড়বে, মেমরি নেবে না, exception ছুড়বে না, locale দেখবে না, শুরুর ফাঁকা জায়গা বাদ দেবে না, আর জানাবে কোথায় থামল ও কোনো ভুল হলো কি না। সবচেয়ে নিচু স্তরের যন্ত্র, যার ওপর অন্য সবকিছু বানানো যায়। একটি বাড়তি প্রতিশ্রুতি আছে দশমিক সংখ্যায়: to_chars এমন সবচেয়ে ছোট লেখা দেয়, যা আবার from_chars দিয়ে পড়লে হুবহু সেই একই bit-এর double ফেরত আসে (round-trip). অর্থাৎ 0.1 লিখলে 0.1, 0.10000000000000001 নয়, অথচ কোনো তথ্যও হারায় না।

এই শেষ প্রতিশ্রুতিটি পূরণ করা সহজ ছিল না। দ্রুত আর নির্ভুলভাবে “সবচেয়ে ছোট round-trip লেখা” বের করা নিয়ে কয়েক দশক ধরে গবেষণা চলেছে; ২০১৮ সালে Ulf Adams-এর Ryu অ্যালগরিদম এই কাজকে অনেক দ্রুত করে। MSVC-র standard library দল Ryu ব্যবহার করে প্রথম দিকের পূর্ণ বাস্তবায়নগুলোর একটি দেয়; অন্য লাইব্রেরিগুলোর দশমিক অংশ সম্পূর্ণ করতে আরও কয়েক বছর লাগে। তাই পুরোনো compiler-এ পূর্ণসংখ্যার from_chars থাকলেও দশমিকেরটি না-ও থাকতে পারে।

মূল উপলব্ধি: “সুবিধাজনক” আর “ভিত্তি” আলাদা জিনিস। সুবিধাজনক যন্ত্র (stoi, stringstream) সিদ্ধান্ত লুকিয়ে রাখে — locale, মেমরি, exception. ভিত্তির যন্ত্র (from_chars) কোনো সিদ্ধান্ত লুকায় না, তাই দ্রুত আর সব জায়গায় একই আচরণ।

প্র ০৫Python বলে “batteries included” — সাথে আসে HTTP, JSON, ZIP, এমনকি ছোট ডেটাবেস। C++-এর standard library-তে এখনো নেটওয়ার্কিং বা JSON নেই কেন? এটা কি দুর্বলতা, না সচেতন সিদ্ধান্ত?

দুটোই কিছুটা। প্রথম কারণ C++ standard-এর প্রকৃতি: এটি একটি আন্তর্জাতিক চুক্তি, যা মানতে হয় ছোট microcontroller থেকে সুপারকম্পিউটার পর্যন্ত সব জায়গার compiler-কে, আর যা একবার ঢোকে তা প্রায় কখনো বদলানো যায় না। কারণ বদলালে পুরোনো কোড ভাঙে, এমনকি আগে compile করা লাইব্রেরির সাথে সামঞ্জস্য (ABI) ভাঙে। std::regex এর বিখ্যাত উদাহরণ: অনেক বাস্তবায়নে এটি ধীর বলে পরিচিত, কিন্তু ABI ভাঙার ভয়ে ভেতরটা নতুন করে লেখা কঠিন। নেটওয়ার্কিংয়ে এই ঝুঁকি আরও বড়: TLS-এর মতো নিরাপত্তা-প্রোটোকল কয়েক বছরেই পুরোনো হয়, নিরাপত্তা-ত্রুটি ঠিক করতে হয় দ্রুত — অথচ C++ standard আসে তিন বছরে একবার।

দ্বিতীয় কারণ বাস্তব ইতিহাস। Christopher Kohlhoff-এর Boost.Asio-র ওপর ভিত্তি করে একটি Networking TS (Technical Specification) ২০১৮ সালে প্রকাশিত হয়েছিল, আর অনেকে ভেবেছিলেন এটি C++20 বা C++23-এ আসবে। কিন্তু কমিটিতে বিতর্ক বাধে অন্তর্নিহিত প্রশ্নে: async কাজ কীভাবে চলবে, কোন executor মডেল সবার জন্য ভিত্তি হবে। সেই ভিত্তি — std::execution (senders/receivers) — শেষ পর্যন্ত C++26-এর জন্য গৃহীত হয়েছে; নেটওয়ার্কিং এখনো তার ওপর দাঁড়ানোর অপেক্ষায়। JSON-এর ক্ষেত্রে প্রশ্ন নকশার: DOM না streaming, কোন সংখ্যার টাইপ, কত বড় ফাইল, কতটা কঠোর পার্সিং — একেক প্রজেক্টের উত্তর একেক রকম, আর nlohmann/json, simdjson-এর মতো বাইরের লাইব্রেরি ইতিমধ্যে ভালো কাজ করছে। একটি 2D graphics প্রস্তাবও বহু বছর আলোচনার পর বাদ পড়েছিল।

অন্য দিকের যুক্তিও শক্ত। নতুন শিক্ষার্থীর জন্য “একটি HTTP অনুরোধ পাঠাও” কাজটি C++-এ বাইরের লাইব্রেরি খোঁজা, build ব্যবস্থায় যোগ করা — এক ঝামেলার শুরু, যা Python-এ এক লাইন। Rust বা Go-র মতো ভাষা এটি সমাধান করেছে আলাদাভাবে: Go standard library-তে অনেক কিছু রেখে, Rust ছোট standard library রেখে কিন্তু cargo দিয়ে বাইরের লাইব্রেরি যোগ খুব সহজ করে। C++-এ vcpkg আর Conan-এর মতো package manager আর CMake (পর্ব ৪৭) সেই ফাঁক অনেকটা পূরণ করছে।

মূল উপলব্ধি: standard library-তে যা ঢোকে, তা চিরকালের প্রতিশ্রুতি। তাই C++ সেখানে রাখে ভিত্তি — container, algorithm, সময়, এলোমেলো সংখ্যা — আর দ্রুত বদলায় এমন জিনিস ছেড়ে দেয় বাইরের লাইব্রেরিকে। কী standard-এ আছে আর কী নেই, দুটিই জানা দরকার।

অনুশীলন

বেশিরভাগ কাজে কোড লিখতে হবে না — আগে অনুমান করুন, তারপর উত্তর মিলিয়ে নিন।

  1. আউটপুট অনুমান: using namespace std::chrono; ধরে নিয়ে নিচের প্রতিটি প্রিন্ট করলে কী আসবে? (ক) duration_cast<minutes>(119s) (খ) round<minutes>(90s) (গ) ceil<minutes>(61s) (ঘ) 3h - 200min

    (ক) 1min — ১১৯ সেকেন্ড = ১.৯৮ মিনিট, duration_cast কেটে ফেলে। (খ) 2min — ১.৫ ঠিক মাঝখানে, round জোড়টি নেয়। (গ) 2min — ceil সবসময় ওপরের দিকে। (ঘ) -20min — ঘণ্টা আর মিনিটের common type মিনিট, ১৮০ − ২০০ = −২০. খেয়াল করুন, (ক)-এ “প্রায় ২ মিনিট” হয়ে গেল ১ মিনিট — timeout হিসাবে এমন কাটছাঁট বিপজ্জনক হতে পারে।

  2. বাগ শিকার: একটি AI এই ফাংশন দিয়েছে — int roll() { srand(time(0)); return rand() % 6 + 1; }. একটি লুপে এটি পরপর ১০ বার ডাকা হলো। কী দেখবেন, আর কয়টি সমস্যা খুঁজে পান?

    সম্ভবত ১০ বারই একই সংখ্যা! প্রতিবার ডাকায় srand আবার একই seed পায় (একই সেকেন্ড), তাই ধারা প্রথম থেকে শুরু হয়ে একই প্রথম মান দেয়। সমস্যাগুলো: (১) প্রতি ডাকে seed দেওয়া; (২) সেকেন্ডভিত্তিক দুর্বল seed; (৩) % 6-এর পক্ষপাত; (৪) rand()-এর অনির্দিষ্ট মান আর global অবস্থা। ঠিক রূপ: প্রোগ্রামের শুরুতে একবার std::mt19937 gen{std::random_device{}()};, আর ফাংশনে std::uniform_int_distribution die{1, 6}; return die(gen); — যেখানে gen reference হিসেবে আসে।

  3. নকশা ভাবুন: একটি অনলাইন পরীক্ষার সার্ভারে চারটি কাজ আছে: (ক) প্রতিটি উত্তর জমার সময় লগে লেখা (খ) একজন পরীক্ষার্থী একটি প্রশ্নে কত সেকেন্ড নিলেন (গ) “পরীক্ষা শুরু হবে ঢাকার সময় সকাল ১০টায়” (ঘ) রেজাল্ট প্রকাশের আর কত দিন বাকি। প্রতিটির জন্য কোন chrono টাইপ বা ঘড়ি বেছে নেবেন?

    (ক) system_clock::now() — আসল মুহূর্ত, UTC-তে রাখুন; দেখানোর সময় স্থানীয় সময়ে আনুন। (খ) steady_clock — শুধু সময়কাল, ঘড়ি বদলালেও ঠিক থাকবে। (গ) zoned_time, "Asia/Dhaka" দিয়ে — স্থানীয় সময়কে নির্দিষ্ট মুহূর্তে রূপান্তর; ভিন্ন দেশের পরীক্ষার্থীর কাছেও ঠিক মুহূর্ত যাবে। (ঘ) year_month_day আর sys_days — ঢাকার আজকের তারিখ থেকে রেজাল্টের তারিখ বিয়োগ। মূল ভাবনা: “কতক্ষণ” আর “কখন” আলাদা প্রশ্ন, আর “কখন”-এর সাথে সবসময় “কোন time zone-এ” প্রশ্নটি আসে।

  4. নিজে লিখুন (কোড): উপরের parse_marks.cpp-এ লাইনটি বদলে "12,7,,30, 5" করুন। কোন কোন ঘর বাদ পড়বে, আর sum কত হবে — আগে অনুমান করুন, তারপর চালিয়ে মিলিয়ে নিন।

    দুটি ঘর বাদ পড়বে: খালি ঘর '' (from_chars কিছুই পড়তে পারে না, ec হয় std::errc::invalid_argument) আর ' 5' (from_chars শুরুর ফাঁকা জায়গা বাদ দেয় না, তাই প্রথম অক্ষরেই থেমে যায়)। sum = 49 (১২ + ৭ + ৩০)। ফাঁকা জায়গাসহ ঘর মেনে নিতে চাইলে পড়ার আগে নিজে ঘরটির দুই পাশের ফাঁকা জায়গা ছেঁটে নিন — সিদ্ধান্তটি আপনার, লাইব্রেরি গোপনে নেয় না।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমC++-এ একটি ফাংশন চলতে কত মিলিসেকেন্ড লাগে, তা মাপার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. <chrono> include করা
  2. steady_clock::now() দিয়ে শুরুর মুহূর্ত রাখা
  3. যে ফাংশন মাপতে চান, সেটি চালানো
  4. আবার steady_clock::now() নিয়ে শুরুর মুহূর্ত বিয়োগ করা
  5. duration_cast<milliseconds> দিয়ে ফল প্রিন্ট করা

C++-এ একটি ফাংশন চলতে কতক্ষণ লাগে, তা মাপার জন্য কোন ঘড়ি সবচেয়ে উপযুক্ত?

  • std::chrono::system_clock
  • std::chrono::high_resolution_clock, সবসময়
  • std::chrono::steady_clock
  • C-এর time(nullptr)

C++-এ (using namespace std::chrono; ধরে নিয়ে) duration_cast<seconds>(1500ms)-এর মান কত?

  • 2s
  • 1s
  • 1.5s
  • Compile error

C++20-এ (2026y / January / 31 + std::chrono::months{1}).ok() কী দেয়?

  • true, কারণ তারিখ নিজে থেকে 2026-02-28 হয়ে যায়
  • true, কারণ তারিখ নিজে থেকে 2026-03-03 হয়ে যায়
  • Compile error
  • false, কারণ 2026-02-31 বৈধ তারিখ নয়

C++-এ rand() % 6 + 1 দিয়ে ছক্কার দান বানানোর মূল গাণিতিক সমস্যা কী?

  • কিছু দান অন্যগুলোর চেয়ে সামান্য বেশি সম্ভাব্য হয়
  • কখনো কখনো ৭ আসে
  • C++-এ rand() ব্যবহার করলে compile error হয়
  • একই seed-এ প্রতিবার আলাদা ধারা আসে

দুটি ভিন্ন C++ standard library-তে (যেমন libstdc++ আর MSVC STL) std::mt19937 gen{42}; দিয়ে শুরু করা হলো। কোনটি নিশ্চিতভাবে দুই জায়গায় একই?

  • uniform_int_distribution{1, 6}(gen)-এর ফল
  • std::shuffle-এর পরে vector-এর ক্রম
  • gen()-এর কাঁচা আউটপুটের ধারা
  • std::random_device{}()-এর মান

C++17-এ "8x" লেখার ওপর std::from_chars দিয়ে একটি int পড়লে কী হয়?

  • std::invalid_argument exception ছোড়ে
  • মান 8, ec সফল, কিন্তু ptr থামে 'x'-এ
  • মান 0, ec সফল
  • ফল প্রোগ্রামের locale-এর ওপর নির্ভর করে

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. নিজে লেখার আগে standard library খুঁজুন — পরীক্ষিত, portable, দ্রুত; মাথায় header-এর মানচিত্র রাখুন।
  2. chrono-তে একক টাইপের অংশ: ক্ষতিহীন রূপান্তর নিজে থেকে, ক্ষতিকর হলে duration_cast (কেটে ফেলে) বা floor/ceil/round.
  3. কতক্ষণ — steady_clock; কখন — system_clock; স্থানীয় তারিখের জন্য time zone ("Asia/Dhaka").
  4. তারিখ: পড়া-লেখায় year_month_day, গোনায় sys_days, আর ব্যবহারকারীর তারিখে সবসময় ok().
  5. random: engine একবার, seed ভেবে-চিন্তে, পক্ষপাতহীন distribution; কাঁচা engine-আউটপুট সব জায়গায় এক, distribution-এর ফল নয়; নিরাপত্তায় CSPRNG. সংখ্যা পড়তে from_chars — ec আর ptr দুটিই দেখুন।

পরের পর্বে: standard library-র পর ভাষা নিজে — C++20 আর C++23-এর বড় নতুন ফিচার: header-এর জায়গায় modules, থেমে আবার চলা যায় এমন coroutines, আর এক অপারেটরে সব তুলনা <=>.

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান