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

C++20/23 — modules, coroutines আর নতুন standard-এর যন্ত্রপাতি

C++20 and C++23 — Modules, Coroutines and the Tools of Modern Standards

Taught in Bangla, this lesson surveys modern standards: the three-year release train, modules and import std, coroutines and std::generator, everyday additions such as source_location, span and bit_cast, C++23 deducing this, mdspan and flat_map, what C++26 brings and checking compiler support with feature-test macros.

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

  • C++ standard কীভাবে তৈরি হয়, তিন বছরের “ট্রেন মডেল” কী, আর কোন standard-এ কী এসেছিল
  • #include কেন ধীর ও “ফুটো”, আর modules (export module, import) কীভাবে সেই সমস্যা সমাধান করে
  • coroutine-এর মূল ধারণা — থামা আর আবার চলা, co_yield, co_await, co_return, coroutine frame — আর std::generator দিয়ে অলস (lazy) মানের সারি
  • C++20/23-এর ছোট কিন্তু কাজের সুবিধা, আর C++26-এ যা গৃহীত হয়েছে
  • compiler কোন বৈশিষ্ট্য সমর্থন করে তা যাচাই করা, আর AI-এর দেওয়া “খুব নতুন” কোড বিচার করা

১ · প্রতি তিন বছরে নতুন C++ — ট্রেনের মডেল

C++ কোনো একটি কোম্পানির পণ্য নয়। ভাষাটির নিয়ম লেখে ISO-র একটি কমিটি, যার আনুষ্ঠানিক নাম WG21. এতে আছেন compiler-নির্মাতা, বড় সফটওয়্যার কোম্পানির প্রকৌশলী, বিশ্ববিদ্যালয়ের গবেষক আর স্বেচ্ছাসেবীরা। তাঁরা প্রস্তাব (paper) লেখেন, বছরে কয়েকবার মিটিংয়ে বসে তর্ক করেন, ভোট দেন। ভোটে পাস হওয়া নিয়মগুলো মিলে তৈরি হয় একটি C++ standard — যেমন C++20 বা C++23. তারপর GCC, Clang আর MSVC-র দল সেই নিয়ম নিজেদের compiler-এ বাস্তবায়ন করে।

আগে এই কাজের কোনো নির্দিষ্ট সময়সূচি ছিল না। প্রথম standard এল ১৯৯৮ সালে (C++98), ২০০৩ সালে তার ভুল সংশোধন (C++03). তারপর কমিটি একটি বড় সংস্করণের কাজ শুরু করল, যার ডাকনাম ছিল “C++0x” — সবাই ধরে নিয়েছিল এটি ২০০০-এর দশকেই আসবে। কিন্তু একের পর এক বৈশিষ্ট্য যোগ হতে থাকল, আর একটি বৈশিষ্ট্য তৈরি না হলে পুরো standard আটকে থাকত। শেষে সেটি বের হলো ২০১১ সালে — C++11 নামে, প্রায় তেরো বছর পর।

কমলাপুর স্টেশনের একটি আন্তঃনগর ট্রেনের কথা ভাবুন। ট্রেন ছাড়ে নির্দিষ্ট সময়ে। কোনো যাত্রী দেরি করলে ট্রেন তাঁর জন্য অপেক্ষা করে না — তিনি পরের ট্রেনে যান। এতে কারও যাত্রা একটু পিছোয়, কিন্তু বাকি সবাই সময়মতো পৌঁছায়। C++11-এর পর কমিটি ঠিক এই নিয়ম নিল: প্রতি তিন বছরে একটি standard ছাড়বে, যে বৈশিষ্ট্য তৈরি থাকবে সে উঠবে, বাকিরা পরের ট্রেনে। একে বলা হয় train model.

এই নিয়মের ফল চোখে দেখা যায়: C++14, C++17, C++20, C++23 — চারটি standard ঠিক তিন বছর পরপর। বড় ও ঝুঁকির বৈশিষ্ট্যগুলো আগে আলাদা একটি পরীক্ষামূলক দলিলে যেত, যার নাম Technical Specification (TS). সেখানে compiler-নির্মাতারা বাস্তবায়ন করে দেখতেন, ব্যবহারকারীরা মতামত দিতেন, তারপর পরিণত হলে মূল standard-এ নেওয়া হতো। coroutines আর modules — দুটিই এই পথে এসেছে। ট্রেনগুলো ধাপে ধাপে দেখুন:

  1. C++98/031998, 2003প্রথম ISO standard
  2. C++112011“আধুনিক C++”-এর জন্ম
  3. C++14/172014, 2017ট্রেন চালু
  4. C++20/232020, 2023বড় চার + গোছানো
  5. C++262026পরের ট্রেন
C++98 আর C++03ভিত্তি
C++98: প্রথম আন্তর্জাতিক standard
  - class, template, exception, namespace
  - STL: vector, map, algorithm, iterator
C++03: মূলত ভুল সংশোধন, নতুন বৈশিষ্ট্য প্রায় নেই
এরপর দীর্ঘ বিরতি — পরের বড় সংস্করণের নাম "C++0x"
C++11প্রায় ১৩ বছরের অপেক্ষা
auto, range-for, lambda
move semantics, rvalue reference
unique_ptr, shared_ptr
std::thread, atomic, constexpr
override, final, nullptr, enum class
শিক্ষা: "সব তৈরি হলে ছাড়ব" = বারবার দেরি
C++14 আর C++17তিন বছরের ছন্দ শুরু
C++14: ছোট পরিমার্জন
  - generic lambda, make_unique, return type deduction
C++17: মাঝারি সংস্করণ
  - optional, variant, any, string_view
  - filesystem, structured bindings, if constexpr
C++20 আর C++23C++11-এর পর সবচেয়ে বড় বদল
C++20 — বড় চার: concepts, ranges, modules, coroutines
  আরও: <=>, consteval, span, format, source_location
C++23 — গোছানো ও পূর্ণ করা:
  print, expected, generator, import std
  mdspan, flat_map, deducing this
C++26যা গৃহীত হয়েছে (মূল তিনটি)
static reflection — compile-এর সময় কোড নিজের গঠন দেখতে পারে
contracts — pre, post, contract_assert
std::execution — async কাজের সাধারণ কাঠামো
compiler-এ এগুলো আসছে ধাপে ধাপে
  1. ভিত্তি তৈরি। C++98 প্রথমবার ভাষাটিকে একটি আন্তর্জাতিক নিয়মের বইয়ে বাঁধল, আর সাথে আনল STL — vector, map, algorithm. C++03 ছিল মূলত ভুল-সংশোধন। এরপর কমিটি একটি বিশাল সংস্করণের পরিকল্পনা করল, কোনো নির্দিষ্ট সময়সীমা ছাড়া।
  2. দেরির শিক্ষা। “C++0x” শেষ পর্যন্ত এল ২০১১ সালে। এতে এমন সব বৈশিষ্ট্য ছিল, যা আজ আমরা স্বাভাবিক ধরে নিই — auto, lambda, move, smart pointer. কিন্তু কমিটি বুঝল, “সব তৈরি হলে তবেই ছাড়ব” নীতিতে প্রতিটি সংস্করণ বছরের পর বছর পিছোবে।
  3. ট্রেন চালু। C++14 এল ঠিক তিন বছর পর, ছোট পরিমার্জন নিয়ে। C++17 মাঝারি — optional, variant, string_view, filesystem. বড় বৈশিষ্ট্যগুলো (concepts, modules, coroutines) তখনো TS হিসেবে পরীক্ষায় ছিল, তাই তারা পরের ট্রেনের অপেক্ষায় রইল।
  4. বড় চারটি একসাথে। C++20-এ concepts, ranges, modules আর coroutines — চারটিই এল। একে প্রায়ই C++11-এর পর সবচেয়ে বড় বদল বলা হয়। C++23 তুলনায় ছোট, কিন্তু সেটি C++20-এর অসম্পূর্ণ অংশগুলো পূরণ করেছে: std::generator ছাড়া C++20-এর coroutine ব্যবহার করা কঠিন ছিল, আর import std; ছাড়া modules অর্ধেক কাজের।
  5. পরের ট্রেন। C++26-এর বৈশিষ্ট্যের তালিকা ২০২৫ সালে বন্ধ হয়েছে। এতে গৃহীত হয়েছে static reflection, contracts আর std::execution — তিনটিই বহু বছরের কাজের ফল। compiler-এ এগুলো আসতে সময় লাগবে, ঠিক যেমন C++20-এর modules-এর বেলায় লেগেছে।
মূল ধারণা

নতুন standard জানার লাভ দুটি। এক, আধুনিক কোড পড়তে পারা — GitHub-এ, অফিসের কোডবেসে বা AI-এর উত্তরে co_yield বা import std; দেখলে আপনি যেন না থমকে যান। দুই, সবচেয়ে সরল যন্ত্রটি বেছে নিতে পারা — অনেক নতুন বৈশিষ্ট্য এসেছে পুরোনো, জটিল কৌশলের জায়গা নিতে। তবে নতুন মানেই সবসময় ব্যবহারযোগ্য নয়: আপনার compiler আর দলের সবার compiler সেটি পারে কি না, সেটিও দেখতে হয়।

২ · যা আগেই শিখেছেন — C++20/23-এর পরিচিত মুখ

এই কোর্সে আপনি না জেনেই C++20/23-এর অনেকগুলো বৈশিষ্ট্য ব্যবহার করেছেন। কোর্সের শুরু থেকে আমরা std::println দিয়ে লিখেছি — সেটি C++23. নিচের তালিকায় প্রতিটির এক লাইনের মনে করিয়ে দেওয়া আর মূল পর্বের লিংক। এগুলো আবার শেখানো হবে না; এই পর্বের মনোযোগ নতুন জিনিসে।

বৈশিষ্ট্যকোন standardএক লাইনেকোথায় শিখেছেন
std::format, std::printC++20, C++23টাইপ-নিরাপদ, {} দিয়ে ফরম্যাট করা আউটপুটপর্ব ০৭
designated initializerC++20struct-এর ঘর নাম ধরে ভরা: {.x = 1}পর্ব ১২
std::spanC++20পাশাপাশি থাকা উপাদানের একটি “জানালা”, মালিক নয়পর্ব ১৩
<=> (spaceship)C++20একটি অপারেটরে সব তুলনা; = default দিয়ে তৈরিপর্ব ২৩
ranges আর viewsC++20 (C++23-এ আরও)container-এর ওপর অলস, | দিয়ে জোড়া পাইপলাইনপর্ব ৩৫
std::expectedC++23হয় একটি মান, নয়তো একটি ভুলের বিবরণ — exception ছাড়াপর্ব ৩৭
consteval, constinitC++20অবশ্যই compile-এর সময় হিসাব; শুরুর মান compile-এর সময়েইপর্ব ৩৯
conceptsC++20template-এর শর্ত নাম দিয়ে লেখা; পরিষ্কার errorপর্ব ৪০
deducing thisC++23member function-এ object-কে স্পষ্ট প্যারামিটার হিসেবে নেওয়াপর্ব ৩৪

বাকি থাকল C++20-এর “বড় চার”-এর দুটি — modules আর coroutines — আর ছোট ছোট কিছু সুবিধা। সেগুলোই এখন।

৩ · Modules — #include-এর পুরোনো সমস্যার উত্তর

পর্ব ১১-এ আপনি header ফাইল দিয়ে প্রোগ্রাম ভাগ করেছেন। কাজটি সহজ মনে হয়, কিন্তু #include-এর ভেতরের কৌশলটি ১৯৭০-এর দশকের C থেকে আসা, আর খুবই সরল: preprocessor header ফাইলের পুরো লেখা হুবহু কপি করে আপনার ফাইলে বসিয়ে দেয়। একে বলে textual inclusion. compiler তারপর সেই বিশাল, জোড়া-লাগানো ফাইলটিকে একটি translation unit হিসেবে পড়ে। এই সরলতার তিনটি দাম আছে।

ধীর build

একটি #include <vector> ভেতরে আরও অনেক header টানে — সব মিলে হাজার হাজার লাইন। প্রজেক্টে ২০০টি .cpp ফাইল থাকলে compiler একই লাইনগুলো ২০০ বার নতুন করে পড়ে আর বোঝে।

একই কাজ বারবার

ফুটো সীমানা

header-এর প্রতিটি macro, প্রতিটি helper আপনার ফাইলে ঢুকে পড়ে। কোনো header #define max(...) করলে আপনার std::max-ও ভেঙে যেতে পারে। header-এর ক্রম বদলালে ফলাফলও বদলাতে পারে।

macro leak

ODR-এর ঝুঁকি

একই header বিভিন্ন ফাইলে ভিন্ন macro সেটিংয়ে include হলে একই নামের জিনিসের দুই রকম definition তৈরি হতে পারে। এটি ODR ভাঙে, আর ফল undefined behavior — প্রায়ই কোনো error ছাড়াই।

নীরব বিপদ

C++ modules এই কৌশলটিই বদলে দেয়। একটি module-এর interface একবার compile হয়ে একটি বিশেষ বাইনারি ফাইলে যায় — একে বলে BMI (built module interface). GCC-তে এর এক্সটেনশন সাধারণত .gcm, Clang-এ .pcm, MSVC-তে .ifc. যে ফাইল import লেখে, compiler তার জন্য লেখা কপি করে না, সরাসরি সেই তৈরি BMI পড়ে নেয়। পার্থক্যটি ধাপে ধাপে দেখুন:

main.cppআপনি যা লেখেন
#include <vector>
#include "shop.h"

int main() { /* ১০ লাইনের কোড */ }
preprocessor-এর পরেcompiler যা পায়
// ---- <vector> আর তার ভেতরের সব header-এর লেখা ----
// ... হাজার হাজার লাইন template, macro, helper ...
// ---- shop.h-এর লেখা ----
#define TAX 15          // এই macro এখন main.cpp-রও
int price_with_vat(int taka);
// ---- আপনার ১০ লাইন ----
int main() { ... }
পুরো প্রজেক্টেএকই কাজ বারবার
a.cpp  → <vector> পড়ো, বোঝো, template তৈরি করো
b.cpp  → <vector> আবার পড়ো, আবার বোঝো
c.cpp  → আবার ...
... ২০০টি ফাইল = ২০০ বার একই header
module: interface একবার compileshop.cppm → BMI
export module shop;                  // module-এর নাম
export int price_with_vat(int taka); // যা বাইরে দেখা যাবে
#define TAX 15                       // module-এর ভেতরেই থাকবে

compiler: shop.cppm  ──►  shop.gcm (বাইনারি, একবারই তৈরি)
import shop;লেখা কপি নয়, তৈরি তথ্য পড়া
import shop;     // shop.gcm পড়ো — আবার parse করতে হয় না

main.cpp যা দেখে:  price_with_vat   (export করা নাম)
main.cpp যা দেখে না: TAX, অন্য helper, ভেতরের macro
import-এর ক্রম বদলালেও অর্থ বদলায় না
  1. দুটি সাধারণ লাইন। দেখতে মনে হয়, আপনি শুধু দুটি নাম “ব্যবহার করতে চান” বলছেন। কিন্তু #include কোনো নাম আনে না — এটি পুরো ফাইলের লেখা আনে।
  2. কপি-পেস্টের ফল। preprocessor সব header-এর লেখা বসিয়ে দেয়, ভেতরের header-গুলোর লেখাও। আপনার ১০ লাইনের ফাইল compiler-এর কাছে হয়ে যায় হাজার হাজার লাইন। আর shop.h-এর TAX macro এখন main.cpp-তেও আছে — আপনি চান বা না চান।
  3. পুনরাবৃত্তি। প্রতিটি .cpp নিজের translation unit, তাই প্রতিটির জন্য একই header নতুন করে পড়া, বোঝা আর template তৈরি হয়। বড় প্রজেক্টে build-এর বড় অংশ সময় যায় এই একই কাজে। precompiled header এই সমস্যা কিছুটা কমায়, কিন্তু ভাষার নিয়মের অংশ নয়।
  4. module-এর interface. export module shop; দিয়ে শুরু ফাইলটি module-এর মুখ। শুধু export করা জিনিসই বাইরের লোক দেখবে। compiler এটি একবার compile করে BMI বানায় — যেখানে তথ্য আগে থেকেই বোঝা ও সাজানো অবস্থায় থাকে।
  5. import মানে তৈরি তথ্য পড়া। import shop; লেখা ফাইল BMI পড়ে, আবার parse করে না। ভেতরের macro বা export না করা helper বাইরে আসে না। ফলে সীমানা পরিষ্কার, ক্রমের ওপর নির্ভরতা নেই, আর অনেক ক্ষেত্রে build দ্রুত। এটাই modules-এর প্রতিশ্রুতি।

একটি ছোট module — interface আর implementation

একটি দোকানের দাম-হিসাবের module লিখে দেখা যাক। প্রথম ফাইলটি module interface unit — যা module-এর বাইরের চেহারা ঘোষণা করে। দ্বিতীয়টি module implementation unit — যেখানে ফাংশনের ভেতরের কাজ থাকে, বাইরে কিছু export করে না। header আর .cpp ফাইলের ভাগের মতোই, শুধু নিয়মগুলো ভাষার ভেতরে।

// shop.cppm — module interface unit (চালানোর জন্য নয়: এক ফাইলের অনলাইন compiler-এ চলবে না)
export module shop;                      // এই ফাইল module "shop"-এর মুখ
import std;                              // C++23: পুরো standard library এক লাইনে

export int price_with_vat(int taka);     // export: যারা import করবে, তারা দেখবে
int round_up(int x);                     // export নেই: module-এর ভেতরের কাজ

// shop_impl.cpp — module implementation unit
module shop;                             // "shop"-এর অংশ; নতুন কিছু export করে না
int round_up(int x) { return (x + 9) / 10 * 10; }
int price_with_vat(int taka) { return round_up(taka * 115 / 100); }

// main.cpp
import std;
import shop;
int main() { std::println("{}", price_with_vat(200)); }   // 230

লক্ষ করুন, round_up main.cpp থেকে ডাকা যাবে না — compiler বলবে নামটি দেখা যাচ্ছে না। header-এর জগতে এমন “গোপন” helper রাখতে static বা anonymous namespace লাগত; module-এ export না করাই যথেষ্ট। বড় module-কে টুকরো করা যায় partition দিয়ে (export module shop:cart;) — এটুকু শুধু নাম শুনে রাখুন। আর পুরোনো header-কেও module-এর মতো import করা যায়: import <vector>; — একে বলে header unit, যদিও এর সমর্থন modules-এর মধ্যেও সবচেয়ে অসম।

import std; — C++23-এর এক লাইনের লাইব্রেরি

C++20 module-এর ভাষাগত নিয়ম দিয়েছিল, কিন্তু standard library তখনো header-ই ছিল। C++23 যোগ করেছে import std; — এক লাইনে পুরো standard library, macro ছাড়া। মজার ব্যাপার হলো, সবকিছু একসাথে import করা প্রায়ই কয়েকটি বড় header include করার চেয়েও দ্রুত, কারণ BMI একবারই তৈরি হয়। (assert-এর মতো macro-গুলো module দিয়ে আসে না; সেগুলোর জন্য এখনো header লাগে। আর std.compat নামে আরেকটি module C-এর ::printf-এর মতো global নামগুলোও দেয়।)

বাস্তব অবস্থা — সমর্থন উন্নত হচ্ছে, তবে এখনো সব জায়গায় সমান নয়। modules-এর সবচেয়ে কঠিন অংশ ভাষা নয়, build. header-এর বেলায় যেকোনো ফাইল যেকোনো ক্রমে compile করা যেত। module-এ আগে interface compile করে BMI বানাতে হয়, তারপর তার ব্যবহারকারীদের — অর্থাৎ build system-কে প্রতিটি ফাইল scan করে ক্রম বের করতে হয়। CMake ৩.২৮ থেকে C++20 module-এর সমর্থন আনুষ্ঠানিক (Ninja বা Visual Studio generator দিয়ে); import std;-এর সমর্থন CMake-এ পরে এসেছে এবং কিছু সংস্করণে এখনো পরীক্ষামূলক। MSVC সবার আগে পরিণত সমর্থন দিয়েছে; GCC আর Clang দ্রুত এগোচ্ছে, কিন্তু সংস্করণভেদে আচরণ আলাদা। তাই নতুন প্রজেক্টে modules চেষ্টা করুন, কিন্তু দলের সবার toolchain আগে যাচাই করুন। (এই পাতার অনলাইন compiler একটিমাত্র ফাইল চালায়, তাই উপরের module কোডটি এখানে চালানো যাবে না। CMake দিয়ে বড় প্রজেক্ট বিল্ড করা পর্ব ৪৭-এ।)

# CMakeLists.txt — module-সহ একটি ছোট প্রজেক্ট (CMake 3.28+, Ninja generator)
cmake_minimum_required(VERSION 3.28)
project(shop CXX)
set(CMAKE_CXX_STANDARD 23)
add_executable(app main.cpp shop_impl.cpp)
target_sources(app PRIVATE FILE_SET CXX_MODULES FILES shop.cppm)
# import std; ব্যবহার করলে CMake-এ আলাদা সেটিং লাগে — আপনার সংস্করণের ডকুমেন্টেশন দেখুন

৪ · Coroutine — যে ফাংশন থেমে আবার চলতে পারে

এ পর্যন্ত আপনার সব ফাংশনের জীবন একই রকম: ডাকা হলো, শুরু থেকে শেষ পর্যন্ত চলল, একবার return করল, শেষ। তার local ভ্যারিয়েবলগুলো stack frame-এ ছিল, ফেরার সঙ্গে সঙ্গে মুছে গেল। পরের বার ডাকলে আবার একদম শুরু থেকে।

একটি coroutine এই নিয়ম ভাঙে। এটি মাঝপথে একটি মান দিয়ে থেমে যেতে পারে (suspend), আর পরে কেউ চাইলে ঠিক সেই জায়গা থেকে আবার চলতে পারে (resume) — local ভ্যারিয়েবলগুলো যেমন ছিল তেমনই রেখে।

একটি ব্যাংকের টোকেন মেশিনের কথা ভাবুন। গ্রাহক বোতাম চাপলেন, মেশিন ছাপল “টোকেন ১০০১” — তারপর থেমে গেল। সে আগে থেকে হাজারটি টোকেন ছেপে রাখে না। পরের গ্রাহক চাপলে মেশিন মনে রাখে শেষবার কোথায় ছিল, আর ছাপে ১০০২. দিনশেষে ব্যাংক বন্ধ হলে বাকি টোকেনগুলো কখনো ছাপাই হয় না। চাইলে তবেই পরের মান, আর থামার জায়গাটি মনে রাখা — এটাই coroutine-এর মূল ভাব।

C++-এ একটি ফাংশন coroutine হয়, যদি তার ভেতরে তিনটি নতুন keyword-এর অন্তত একটি থাকে:

keywordমানেটোকেন মেশিনে
co_yield xএকটি মান ডাকা-পক্ষকে দাও, তারপর থামোএকটি টোকেন ছেপে অপেক্ষা
co_await eকোনো কাজ শেষ হওয়ার অপেক্ষায় থামো; শেষ হলে এখান থেকে চলোকাগজ ফুরোলে নতুন রোল আসার অপেক্ষা
co_return xcoroutine শেষ; চাইলে একটি শেষ ফল দাওদিনের কাজ শেষ

coroutine-এ সাধারণ return লেখা যায় না, আর এর ফেরত-টাইপ হতে হয় বিশেষ ধরনের — যে টাইপ জানে কীভাবে থামা-চলা সামলাতে হয়। C++20 ভাষার যন্ত্রটি দিয়েছিল, কিন্তু কোনো তৈরি ফেরত-টাইপ দেয়নি। একটি সাধারণ generator লিখতেও নিজে একটি promise type, একটি std::coroutine_handle আর iterator মিলিয়ে কয়েক ডজন লাইন লিখতে হতো — তাই অনেকে coroutine দেখে ভয় পেতেন। C++23-এর std::generator সেই কাজ তৈরি করে দিয়েছে। এবার একটি চালানো যায় এমন উদাহরণ: দোকানের invoice নম্বর, একটি একটি করে, শুধু যখন দরকার।

#include <generator>
#include <print>

// A coroutine: pauses at each co_yield, resumes when the caller wants more
std::generator<int> invoice_numbers(int first) {
    for (int n = first; ; ++n) {             // an endless loop is fine here
        std::println("  [generator] making {}", n);
        co_yield n;                           // hand n to the caller, then pause
    }
}

int main() {
    int printed = 0;
    for (int inv : invoice_numbers(1001)) {   // each value is made on demand
        std::println("caller got INV-{}", inv);
        if (++printed == 3) break;            // stop: 1004 is never made
    }
}

আউটপুট ছয় লাইন, পালা করে: [generator] making 1001, caller got INV-1001, তারপর 1002-এর দুই লাইন, তারপর 1003-এর দুই লাইন। লক্ষ করুন — লুপটি অসীম, for (int n = first; ; ++n), তবু প্রোগ্রাম আটকে যায়নি। কারণ generator আগে থেকে সব নম্বর বানায় না; প্রতিটি co_yield-এ থেমে যায়, আর ডাকা-পক্ষ পরের মান চাইলে তবেই আবার চলে। ডাকা-পক্ষ তিনটির পর break করেছে, তাই 1004 কখনো তৈরিই হয়নি। আর n-এর মান দুই ডাকের মাঝে হারায়নি — প্রতিবার আগের জায়গা থেকেই ১ বাড়ল। এই আচরণ পর্ব ৩৫-এর views-এর মতোই lazy evaluation. (<generator> GCC ১৪ থেকে libstdc++-এ আছে; আপনার compiler-এ না থাকলে ৮ নম্বর অংশে যাচাইয়ের উপায় দেখুন।)

দুই পক্ষের মধ্যে নিয়ন্ত্রণ কীভাবে হাত বদলায়, ধাপে ধাপে দেখুন। প্রতিটি ধাপে আগে অনুমান করুন, পরের আউটপুট লাইন কোনটি:

1generator<int> invoice_numbers(int first) {2    for (int n = first; ; ++n) {3        println("  [generator] making {}", n);4        co_yield n;5    }6}7for (int inv : invoice_numbers(1001)) {8    println("caller got INV-{}", inv);9    if (++printed == 3) break;10}
এই মুহূর্তে
কে চলছে
generator-এর n
coroutine frame
printed
  1. ডাকলেই চলে না। invoice_numbers(1001) ডাকার সঙ্গে সঙ্গে একটি coroutine frame তৈরি হয়, প্যারামিটার first সেখানে জমা হয় — কিন্তু ফাংশনের শরীরের একটি লাইনও চলে না। std::generator শুরুতেই থেমে থাকে। কোনো আউটপুট নেই।
  2. প্রথম মান চাওয়া। range-for প্রথম মান চাইল, তাই generator প্রথমবার চলল: n = 1001, “making 1001” ছাপল, তারপর co_yield n — মানটি হাতে দিয়ে থেমে গেল। ফাংশনটি শেষ হয়নি, শুধু থেমেছে।
  3. এবার caller-এর পালা। লুপের শরীর চলল, inv = 1001 ছাপা হলো, printed হলো 1. এই পুরো সময়টা generator ঘুমিয়ে আছে, তার n frame-এ নিরাপদে জমা।
  4. পালা বদল। পরের মান চাওয়া হলো। generator ঠিক co_yield-এর পরের জায়গা থেকে জাগল, ++n করে 1002, ছাপল, আবার থামল। তারপর caller ছাপল INV-1002. কোনো ফাংশন “শুরু থেকে” চলেনি — দুজনেই নিজের জায়গা মনে রেখেছে।
  5. তৃতীয় পাক। একই ছন্দে 1003. এবার ++printed == 3 সত্য, তাই caller break করবে।
  6. শেষ — বাকিটা কখনো তৈরি হয় না। লুপ থেকে বেরোতেই generator object ধ্বংস হলো, আর সে তার coroutine frame মুছে দিল — coroutine তখনো co_yield-এ থেমে ছিল, সেখানেই তার জীবন শেষ। 1004 কখনো বানানো হয়নি। অসীম লুপ, কিন্তু কাজ হয়েছে ঠিক যতটুকু দরকার।

Coroutine frame — থামার সময় অবস্থা থাকে কোথায়?

সাধারণ ফাংশনের local ভ্যারিয়েবল থাকে stack frame-এ, যা ফেরার সময় মুছে যায়। কিন্তু coroutine থামার পর caller অন্য কাজ করে, অন্য ফাংশন ডাকে — stack-এর সেই জায়গা অন্যরা ব্যবহার করে ফেলে। তাই coroutine-এর অবস্থা রাখতে হয় এমন জায়গায়, যা থামার পরেও টিকে থাকে। একে বলে coroutine frame: এতে থাকে প্যারামিটারের কপি, যেসব local ভ্যারিয়েবল থামার পরেও লাগবে, আর “কোথায় থেমেছিলাম” তার চিহ্ন। frame সাধারণত heap-এ তৈরি হয়। তবে compiler যদি প্রমাণ করতে পারে যে coroutine-এর জীবন caller-এর ভেতরেই শেষ হবে, তাহলে সে এই heap বরাদ্দ বাদ দিতে পারে (HALO নামে পরিচিত অপ্টিমাইজেশন) — কিন্তু এর নিশ্চয়তা নেই।

frame-এ থাকে প্যারামিটারের কপি। প্যারামিটার যদি reference বা std::string_view হয়, frame-এ থাকে শুধু সেই reference — আসল জিনিসটি নয়। caller-এর অস্থায়ী string ইতিমধ্যে মরে গেলে coroutine পরে জেগে একটি dangling reference ছোঁবে (পর্ব ১৮-এর lifetime-এর বিপদ, নতুন রূপে)। নিয়ম: যে coroutine পরে চলবে, তাকে মালিকানাসহ মান দিন — std::string, const std::string& নয়।

কোথায় কাজে লাগে

এক, অলস সারি (lazy sequence) — যেমন উপরের invoice নম্বর, একটি বিশাল ফাইলের লাইন একটি একটি করে পড়া, বা একটি গাছের নোডগুলো ক্রমে ঘোরা। std::generator একটি range, তাই পর্ব ৩৫-এর view দিয়ে জুড়ে দেওয়া যায়: invoice_numbers(1001) | std::views::take(3).

দুই, async I/O — এখানেই coroutine-এর আসল শক্তি। নেটওয়ার্ক থেকে ডেটা আসতে সময় লাগে। পুরোনো পদ্ধতিতে হয় thread আটকে অপেক্ষা করতে হতো, নয়তো callback-এর ভেতরে callback লিখতে হতো। coroutine দিয়ে কোড দেখতে সোজা, ধাপে ধাপে — অথচ প্রতিটি co_await-এ thread ছাড়া পেয়ে অন্য কাজ করে:

// Asio লাইব্রেরি দিয়ে একটি echo server-এর অংশ — ধারণা দেখাতে, এখানে চালানোর জন্য নয়
asio::awaitable<void> echo(tcp::socket s) {
    char buf[1024];
    for (;;) {
        auto n = co_await s.async_read_some(asio::buffer(buf), asio::use_awaitable);
        co_await asio::async_write(s, asio::buffer(buf, n), asio::use_awaitable);
    }
}

এখানে co_await-এ coroutine থামে, আর ডেটা এলে Asio তাকে আবার জাগায়। একটি thread এভাবে হাজার হাজার সংযোগ সামলাতে পারে। standard library এখনো নিজে কোনো async I/O দেয় না; এজন্য Asio, cppcoro-র মতো লাইব্রেরি ব্যবহার হয়। আপনার কাজ coroutine লাইব্রেরি লেখা নয় — তৈরি লাইব্রেরি ঠিকভাবে ব্যবহার করা। thread আর async-এর মূল ধারণা পর্ব ৪৫-৪৬-এ আসবে।

৫ · ছোট কিন্তু প্রতিদিনের — C++20/23-এর সুবিধা

বড় বৈশিষ্ট্য নিয়ে আলোচনা বেশি হয়, কিন্তু রোজকার কোড সবচেয়ে বেশি বদলায় ছোট সুবিধাগুলো। প্রথমে দুটি একসাথে, একটি চালানো যায় এমন প্রোগ্রামে: পরীক্ষার নম্বরের একটি অংশ std::span দিয়ে পাঠানো, আর std::source_location দিয়ে এমন একটি log ফাংশন, যা নিজে থেকেই জানে কোন লাইন থেকে তাকে ডাকা হয়েছে।

#include <print>
#include <source_location>
#include <span>

void log(const char* msg, std::source_location at = std::source_location::current()) {
    std::println("[line {}] {}: {}", at.line(), at.function_name(), msg);
}

int average(std::span<const int> marks) {      // a view: no copy, knows its size
    log("averaging");
    int sum = 0;
    for (int m : marks) sum += m;
    return marks.empty() ? 0 : sum / static_cast<int>(marks.size());
}

int main() {
    int marks[] = {72, 85, 64, 90, 58, 77};
    std::span<const int> all{marks};
    std::println("first 3: {}", average(all.first(3)));
    std::println("last 2: {}", average(all.last(2)));
    log("done");
}

GCC-তে আউটপুট পাঁচ লাইন: [line 10] int average(std::span<const int>): averaging, first 3: 73, আবার একই log লাইন, last 2: 67, আর [line 21] int main(): done. জাদুটা default argument-এ: std::source_location::current() হিসাব হয় ডাকার জায়গায়, log-এর ভেতরে নয়। তাই log পায় caller-এর লাইন আর ফাংশনের নাম। পুরোনো দিনে এজন্য __LINE__ আর __FILE__ দিয়ে একটি macro লিখতে হতো, কারণ সাধারণ ফাংশনে লিখলে সবসময় log-এর নিজের লাইনই আসত। এখন এটি একটি সাধারণ, টাইপ-নিরাপদ ফাংশন; at.file_name() দিয়ে ফাইলের নামও পাওয়া যায়। (function_name()-এর লেখা কেমন হবে, তা compiler ঠিক করে — MSVC বা Clang-এ একটু অন্যরকম দেখাতে পারে।) অন্যদিকে all.first(3) আর all.last(2) নতুন কোনো array বানায়নি — একই মেমরির ওপর দুটি ছোট জানালা: (72+85+64)/3 = 73, (58+77)/2 = 67.

এবার আরও কিছু ছোট সুবিধা, এক জায়গায়। এগুলো GCC-র সাম্প্রতিক সংস্করণে যাচাই করা, তবে এখানে চালানোর জন্য নয়:

// C++20: using enum — switch-এ বারবার Color:: লিখতে হয় না
enum class Color { red, green, blue };
std::string_view name(Color c) {
    switch (c) {
        using enum Color;
        case red:   return "red";
        case green: return "green";
        case blue:  return "blue";
    }
    return "?";
}

// C++20: [[likely]] / [[unlikely]] — কোন শাখা বেশি ঘটে, compiler-কে ইঙ্গিত
if (payment_ok) [[likely]] { ship(); } else [[unlikely]] { alert(); }

// C++20: std::bit_cast — বিটগুলো হুবহু রেখে টাইপ বদল (memcpy-র নিরাপদ রূপ)
auto bits = std::bit_cast<std::uint32_t>(1.0f);     // 0x3f800000

// contains, starts_with, ends_with
stock.contains("rice");          // map/set: C++20
id.starts_with("INV-");          // string, string_view: C++20
id.ends_with("01");              // C++20
id.contains("10");               // string, string_view: C++23

// C++20: std::ssize — signed আকার, উল্টো লুপে নিরাপদ
for (auto i = std::ssize(v) - 1; i >= 0; --i) { /* ... */ }
  • using enum (C++20): পর্ব ১২-এর enum class নিরাপদ, কিন্তু প্রতিবার Color::red লেখা ক্লান্তিকর। একটি scope-এর ভেতরে using enum Color; লিখলে সেখানে শুধু red লিখলেই চলে — নিরাপত্তা বজায় থাকে, কারণ এটি শুধু সেই ছোট scope-এ খাটে।
  • [[likely]] / [[unlikely]] (C++20): পর্ব ৮-এর attribute-এর পরিবারে নতুন সদস্য। compiler এই ইঙ্গিতে কোড এমনভাবে সাজাতে পারে যাতে বেশি-ঘটা পথ দ্রুত হয়। এটি শুধু ইঙ্গিত, আদেশ নয় — আর ভুল ইঙ্গিত দিলে কোড ধীরও হতে পারে। মেপে না দেখে (পর্ব ৪৯) ছড়িয়ে দেবেন না।
  • std::bit_cast (C++20): একটি float-এর বিটগুলো পূর্ণসংখ্যা হিসেবে দেখার পুরোনো কৌশল ছিল pointer cast বা union — যা C++-এ undefined behavior. এই কাজকে বলে type punning. bit_cast এটি বৈধভাবে করে, শর্ত হলো দুই টাইপের আকার সমান; আর এটি constexpr-ও।
  • contains(), starts_with(), ends_with(): m.find(k) != m.end() বা s.rfind("INV-", 0) == 0-এর মতো ঘুরপথের বদলে সরাসরি প্রশ্ন। কোনটি কোন standard-এ, খেয়াল রাখুন — map-এর contains C++20, string-এর contains C++23.
  • std::ssize (C++20): v.size() unsigned, তাই খালি vector-এ v.size() - 1 বিশাল একটি সংখ্যা হয়ে যায় (পর্ব ৬)। std::ssize(v) signed মান দেয়, তাই উল্টো লুপ নিরাপদ।
  • designated initializer (C++20, পর্ব ১২-এ দেখেছেন): Order o{.id = 7, .qty = 2}; — কোন মান কোন ঘরে, নাম দেখেই বোঝা যায়। মনে রাখুন, C++-এ ঘরগুলো ঘোষণার ক্রমেই লিখতে হয়।

৬ · C++23-এর আরও কয়েকটি নতুন যন্ত্র

C++23-কে অনেকে “গোছানোর standard” বলেন, কিন্তু এতে কয়েকটি সত্যিকারের নতুন যন্ত্রও আছে। এগুলো এক ঝলকে চিনে রাখুন — কোনোটিই আপনাকে আজ ব্যবহার করতে হবে না, কিন্তু আধুনিক কোডে দেখলে চিনতে হবে।

// বহুমাত্রিক subscript: operator[] এখন একাধিক প্যারামিটার নিতে পারে
struct Grid {
    std::vector<int> d; int cols;
    int& operator[](int r, int c) { return d[r * cols + c]; }   // g[r, c]
};

// std::mdspan: পাশাপাশি থাকা ডেটার ওপর বহুমাত্রিক view (মালিক নয়)
std::vector<int> seats(3 * 4);
std::mdspan hall(seats.data(), 3, 4);     // ৩ সারি × ৪ আসন
hall[1, 2] = 1;                           // সারি 1, আসন 2

// std::flat_map: ভেতরে দুটি সাজানো vector — ছোট map-এ দ্রুত খোঁজা
std::flat_map<std::string, int> price{{"rice", 70}, {"dal", 120}};

// deducing this: object-কে স্পষ্ট প্যারামিটার হিসেবে নেওয়া
struct Account { int bal = 0; int get(this const Account& self) { return self.bal; } };

// auto(x): x-এর একটি নতুন কপি, টাইপ নিজে না লিখে
some_function(auto(v));                   // v নয়, v-এর একটি অস্থায়ী কপি পাঠাও
  • multidimensional subscript: আগে m[i][j] লিখতে proxy object বানাতে হতো, বা m(i, j) দিয়ে কাজ চালাতে হতো। C++23 থেকে operator[] একাধিক প্যারামিটার নিতে পারে, তাই m[i, j] সরাসরি লেখা যায়। (C++20-এ এই লেখা একটি comma operator হিসেবে পড়া হতো, যা deprecated করে জায়গা খালি করা হয়েছিল।)
  • std::mdspan: পর্ব ১৩-এ এর উল্লেখ দেখেছেন। একটি এক-লাইনের মেমরিকে ম্যাট্রিক্স বা ঘনকের মতো দেখার জানালা — বৈজ্ঞানিক হিসাব, ছবি প্রক্রিয়া, মেশিন লার্নিংয়ে কাজে লাগে।
  • std::flat_map: পর্ব ৩১-এ উল্লেখ করা। গাছের বদলে সাজানো vector, তাই cache-বান্ধব আর খোঁজা দ্রুত; কিন্তু মাঝখানে ঢোকানো ধীর। একবার তৈরি করে বারবার পড়ার কাজে ভালো।
  • deducing this: পর্ব ৩৪-এ lambda-র নিজেকে ডাকার কৌশলে দেখেছেন। এর বড় লাভ হলো const আর non-const সংস্করণের মতো দুইবার একই ফাংশন লেখার প্রয়োজন কমানো, আর পর্ব ২৫-এ উল্লেখ করা CRTP-এর অনেক ব্যবহার সরল করা।
  • auto(x): “এর একটি নতুন কপি দাও” — টাইপের নাম না লিখে। template কোডে, যেখানে টাইপ লম্বা বা অজানা, সেখানে কাজে লাগে।

৭ · সামনে কী — C++26

C++26-এর বৈশিষ্ট্যের তালিকা ২০২৫ সালে বন্ধ হয়েছে, আর সেখানে তিনটি বড় জিনিস গৃহীত হয়েছে, যা নিয়ে কমিটি বহু বছর কাজ করেছে। compiler-এ এগুলো আসছে ধীরে ধীরে, তাই আজ নিজের কোডে ব্যবহারের আগে সমর্থন যাচাই করুন।

Static reflection

compile-এর সময় প্রোগ্রাম নিজের গঠন দেখতে পারবে — একটি struct-এ কোন কোন ঘর আছে, একটি enum-এর মানগুলোর নাম কী। তাতে enum থেকে string, JSON-এ রূপান্তর বা serialization-এর কোড হাতে না লিখে তৈরি করা যাবে। একে বলে static reflection.

C++26-এ গৃহীত

Contracts

ফাংশনের শর্ত কোডে লেখা: pre(x > 0), post(r: r >= 0), আর contract_assert. শর্ত ভাঙলে কী হবে, তা build-এর সময় বেছে নেওয়া যায়। এই contracts নিয়ে কমিটিতে তীব্র বিতর্ক হয়েছে।

C++26-এ গৃহীত

std::execution

async আর সমান্তরাল কাজের একটি সাধারণ কাঠামো — sender আর receiver নামে। কোন কাজ কোথায় (কোন thread বা GPU-তে) চলবে আর কাজগুলো কীভাবে জুড়বে, তার মানক ভাষা। একে বলে std::execution.

C++26-এ গৃহীত

এর বাইরেও C++26-এ এসেছে #embed (একটি বাইনারি ফাইল সরাসরি array-তে বসানো), std::inplace_vector (নির্দিষ্ট সর্বোচ্চ আকারের, heap ছাড়া vector), আর নিরাপত্তার দিকে বড় পদক্ষেপ: অশুরু (uninitialized) ভ্যারিয়েবল পড়া এখন আর পুরোপুরি undefined behavior নয়, বরং “erroneous behavior” — যা পর্ব ৪৩-এর আলোচনার সঙ্গে সরাসরি যুক্ত। অন্যদিকে pattern matching (inspect/match) আর নিরাপত্তার “profiles” — এগুলো এখনো প্রস্তাবিত, C++26-এর অংশ নয়।

C++20-এর দুটি বড় বৈশিষ্ট্যের পেছনে দুটি দীর্ঘ যাত্রা। modules-এর প্রথম গুরুত্বপূর্ণ প্রস্তাব লেখেন EDG কোম্পানির Daveed Vandevoorde, ২০০৫ সালের দিকে। পরে Microsoft-এর Gabriel Dos Reis-এর নকশা Modules TS হয় (২০১৮), আর Google-এর Richard Smith-এর Clang-ভিত্তিক অভিজ্ঞতা মিলিয়ে ২০১৯ সালের শুরুতে সেটি C++20-এ ঢোকে — প্রায় পনেরো বছরের পথ। coroutines-এর প্রধান স্থপতি Microsoft-এর Gor Nishanov; তাঁর নকশা Coroutines TS হয় ২০১৭ সালে, আর একই ২০১৯ সালের মিটিংয়ে C++20-এ গৃহীত হয়।

৮ · আপনার compiler কী পারে — যাচাইয়ের উপায়

standard-এ একটি বৈশিষ্ট্য থাকা আর আপনার compiler-এ সেটি থাকা — দুটি আলাদা কথা। C++20 প্রকাশিত হয় ২০২০ সালে, কিন্তু তার সব বৈশিষ্ট্য সব বড় compiler-এ আসতে কয়েক বছর লেগেছে, আর modules-এর বেলায় এখনো পুরো হয়নি। তাই দুটি অভ্যাস গড়ে তুলুন।

এক, compiler support table দেখা। cppreference-এর “C++ compiler support” পাতায় প্রতিটি বৈশিষ্ট্যের পাশে লেখা থাকে GCC, Clang আর MSVC-র কোন সংস্করণ থেকে সেটি আছে। একটি গুরুত্বপূর্ণ সূক্ষ্মতা: ভাষার বৈশিষ্ট্য (যেমন coroutine keyword) আসে compiler থেকে, আর লাইব্রেরির বৈশিষ্ট্য (যেমন std::generator) আসে standard library থেকে — GCC-র সঙ্গে libstdc++, Clang-এর সঙ্গে সাধারণত libc++ বা libstdc++. Clang compiler নতুন হলেও পুরোনো লাইব্রেরি থাকলে লাইব্রেরির বৈশিষ্ট্য না-ও পেতে পারেন।

দুই, feature-test macro ব্যবহার। প্রতিটি নতুন বৈশিষ্ট্যের একটি macro আছে, যা সমর্থন থাকলে define করা থাকে, আর তার মান একটি তারিখ (বছর ও মাস)। লাইব্রেরির macro-গুলো একসাথে পাওয়া যায় C++20-এর <version> header-এ। এভাবে কোড নিজেই ঠিক করতে পারে, কোন পথে যাবে:

যাচাইয়ের কোডcompile-এর সময় সিদ্ধান্ত
#include <version>               // সব লাইব্রেরি macro এখানে

#if defined(__cpp_lib_generator) && __cpp_lib_generator >= 202207L
    #include <generator>
    // std::generator দিয়ে অলস সারি
#else
    // বিকল্প: একটি vector-এ আগে থেকে নম্বরগুলো বানানো
#endif
নতুন GCC (libstdc++ 14+)macro আছে
__cpp_lib_generator  =  202207L

=> শর্ত সত্য, প্রথম শাখা compile হয়
   #else-এর অংশ compiler দেখেই না
পুরোনো লাইব্রেরিmacro নেই
__cpp_lib_generator  ->  define করা নেই

=> #else শাখা compile হয়
   #include <generator> লাইনটি কখনো পড়াই হয় না,
   তাই "file not found" error-ও আসে না
macro-র মান কেন তারিখসংস্করণ বোঝা
__cpp_lib_ranges       202110L, 202202L, ... বৈশিষ্ট্য বাড়লে মান বাড়ে
__cpp_concepts         ভাষার বৈশিষ্ট্য: <version> ছাড়াই আছে
__cplusplus            শুধু standard-এর মোটা দাগ (যেমন 202302L)
                        — একটি নির্দিষ্ট বৈশিষ্ট্য আছে কি না, তা বলে না
  1. প্রশ্নটি কোডে লেখা। <version> একটি হালকা header, যাতে শুধু এই macro-গুলো আছে। #if দিয়ে জিজ্ঞেস করা হলো: generator আছে কি না, আর থাকলে অন্তত ২০২২ সালের জুলাইয়ের সংস্করণ কি না।
  2. সমর্থন আছে। নতুন GCC-তে macro-র মান 202207L, তাই প্রথম শাখা compile হয়। preprocessor #else-এর অংশ ফেলে দেয় — compiler সেটি দেখেই না।
  3. সমর্থন নেই। পুরোনো লাইব্রেরিতে macro-টি define করাই থাকে না। শর্ত মিথ্যা, তাই #include <generator> লাইনটিও বাদ পড়ে — নইলে সেখানেই “file not found” error হতো। একই কোড দুই রকম toolchain-এ চলে।
  4. মান কেন তারিখ। কোনো বৈশিষ্ট্য পরের standard-এ উন্নত হলে তার macro-র মান বাড়ে, তাই >= দিয়ে নির্দিষ্ট সংস্করণ চাওয়া যায়। ভাষার বৈশিষ্ট্যের macro (__cpp_concepts) header ছাড়াই থাকে। আর __cplusplus শুধু বলে compiler কোন standard মোডে আছে — কোনো নির্দিষ্ট বৈশিষ্ট্য সত্যিই বাস্তবায়িত কি না, তা নয়।

বেশিরভাগ ক্ষেত্রে এত জটিলতার দরকার নেই — প্রজেক্টের জন্য একটি ন্যূনতম compiler সংস্করণ ঠিক করে নিলেই চলে। feature-test macro সবচেয়ে বেশি কাজে লাগে লাইব্রেরি-লেখকদের, যাঁদের কোড অনেক রকম compiler-এ চলতে হয়। আর দ্রুত পরীক্ষা করতে Compiler Explorer-এর মতো অনলাইন টুলে কয়েকটি compiler পাশাপাশি চালিয়ে দেখা সবচেয়ে সহজ।

C জানলে

C-তেও #include হুবহু একই textual কপি, আর macro ফাঁসের সমস্যাও একই — তাই modules যে সমস্যার সমাধান করে, C প্রোগ্রামাররা তা ভালোভাবেই চেনেন। পার্থক্য হলো, C-তে modules বা coroutine-এর মতো কিছু নেই; C-ও নিজের standard (C11, C17, C23) নিয়ে এগোয়, কিন্তু অনেক ধীরে ও ছোট পদক্ষেপে। C-তে header আর macro কীভাবে কাজ করে তা বিস্তারিত দেখতে C কোর্সের preprocessor ও macro আর C23-এর নতুন দিক জানতে আধুনিক C পর্ব পড়ুন।

পরীক্ষা ও ইন্টারভিউতে
  • C++20-এর “বড় চার” কী কী? — concepts, ranges, modules আর coroutines.
  • #include আর import-এর মূল পার্থক্য কী? — #include header-এর লেখা কপি করে বসায়; import আগে থেকে compile করা module interface পড়ে, আর macro বা export না করা নাম বাইরে আনে না।
  • কোন ফাংশন coroutine? — যার শরীরে co_await, co_yield বা co_return আছে; সেখানে সাধারণ return চলে না।
  • coroutine থামলে local ভ্যারিয়েবল কোথায় থাকে? — coroutine frame-এ, যা সাধারণত heap-এ তৈরি হয় (compiler কখনো কখনো বরাদ্দটি বাদ দিতে পারে)।
  • একটি বৈশিষ্ট্য compiler সমর্থন করে কি না, কোডে কীভাবে বুঝবেন? — feature-test macro দিয়ে, যেমন __cpp_lib_generator; লাইব্রেরির macro-গুলো <version> header-এ থাকে।

৯ · AI-এর যুগে — “খুব নতুন” কোডের ফাঁদ

নতুন standard-এর বেলায় AI-এর ভুলগুলো একটু আলাদা ধরনের। পুরোনো বিষয়ে AI-এর কাছে লাখ লাখ উদাহরণ আছে; কিন্তু C++23-এর মতো নতুন বিষয়ে উদাহরণ কম, আর অনলাইনের অনেক লেখায় প্রস্তাব আর চূড়ান্ত নিয়ম মিশে আছে। তাই AI প্রায়ই এমন কোড দেয়, যা “কাগজে” ঠিক, কিন্তু আপনার compiler-এ চলে না — বা এমন বৈশিষ্ট্য দেখায়, যা আসলে এখনো কেবল প্রস্তাব। সাধারণ ভুলগুলো:

AI যা করেসমস্যাযা করবেন
-std=c++20 বলে std::generator, std::print বা std::expected ব্যবহার করেএগুলো C++23; C++20 মোডে compile হবে নাপ্রতিটি বৈশিষ্ট্যের standard যাচাই করুন
সরল একটি generator-এর জন্য নিজে promise_type, coroutine_handle লেখেকয়েক ডজন লাইন, lifetime-এর ভুলের বড় সুযোগC++23 থাকলে std::generator
import std; লিখে বলে “শুধু compile করুন”বেশিরভাগ toolchain-এ বাড়তি flag বা build সেটিং লাগেtoolchain-এর module সমর্থন আগে দেখুন
প্রস্তাবিত বৈশিষ্ট্য (pattern matching) বা অন্য ভাষার syntax-কে C++ বলে দেখায়এমন কোড কোনো compiler-এ চলে নাcppreference-এ নিশ্চিত হোন
coroutine-এ const std::string& বা string_view প্যারামিটার নেয়caller-এর অস্থায়ী মান মরে গেলে danglingপরে চলা coroutine-এ by value
প্রতিটি if-এ [[likely]] বসায়মাপা ছাড়া ইঙ্গিত; ভুল হলে ধীরশুধু মেপে দেখা গরম পথে

AI-কে নতুন বৈশিষ্ট্যের কোড লিখতে বলার সময় আপনার toolchain স্পষ্ট করে দিন, আর প্রতিটি বৈশিষ্ট্যের standard জানতে চান:

আমার toolchain: g++ (GCC 14), -std=c++23 -Wall -Wextra, একটিমাত্র .cpp ফাইল, কোনো build system নেই। একটি দোকানের জন্য invoice নম্বর তৈরির কোড লেখো: নম্বর 1001 থেকে শুরু, প্রতিটি "INV-1001" ধরনের লেখা, শুধু যতগুলো দরকার ততগুলো তৈরি হবে। নিয়ম: ১. coroutine লাগলে std::generator ব্যবহার করবে; নিজে promise_type বা coroutine_handle লিখবে না। ২. import std বা অন্য কোনো module ব্যবহার করবে না, শুধু #include. ৩. coroutine-এর প্যারামিটার by value নেবে, reference বা string_view নয় — কেন, এক লাইনে বলবে। ৪. কোডের পরে একটি তালিকা দাও: যে যে বৈশিষ্ট্য ব্যবহার করেছ, প্রতিটি কোন standard-এ এসেছে (C++20 না C++23), আর GCC-র কোন সংস্করণ থেকে আছে বলে তুমি মনে করো। ৫. কোনো বিষয়ে নিশ্চিত না হলে "নিশ্চিত নই" লিখবে, অনুমান করে সংখ্যা দেবে না।

নতুন C++ কোড যাচাইয়ের চেকলিস্ট

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

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

প্র ০১modules-এর প্রথম গুরুতর প্রস্তাব এসেছিল ২০০৫ সালের দিকে, অথচ C++20-এ ঢুকতে লাগল প্রায় পনেরো বছর। একটি “সহজ” ধারণা এত দেরি করল কেন?

ধারণাটি সহজ শোনায়: “লেখা কপি না করে compile করা interface পড়ো।” অন্য অনেক ভাষায় — Pascal-এর unit, Ada-র package, পরে Java আর C#-এ — এমন ব্যবস্থা বহু আগে থেকেই আছে। কিন্তু C++-এর সমস্যা হলো, তাকে নতুন ব্যবস্থা আনতে হয় কয়েক দশকের পুরোনো, কোটি কোটি লাইনের header-ভিত্তিক কোড না ভেঙে। EDG-র Daveed Vandevoorde ২০০৫ সালের দিকে প্রথম বিস্তারিত প্রস্তাব লেখেন। তখন কমিটি C++0x-এর বিশাল কাজে ব্যস্ত, আর modules-কে পরের জন্য রাখা হয়।

আসল কঠিন প্রশ্নগুলো ছিল macro আর template নিয়ে। header-এর বহু কোড macro-র ওপর নির্ভর করে — একটি module কি macro রপ্তানি করবে? করলে textual include-এর সব বিপদ ফিরে আসে; না করলে পুরোনো লাইব্রেরি রূপান্তর কঠিন। template-এর বেলায়, তার পুরো definition ব্যবহারকারীর কাছে পৌঁছাতে হয়, তাই BMI-তে template কীভাবে রাখা হবে আর কোন নাম কোথায় দেখা যাবে — এই নিয়ম লেখা জটিল। এরপর দুটি আলাদা ধারা তৈরি হলো। Microsoft-এ Gabriel Dos Reis একটি পরিষ্কার, macro-বিহীন নকশা এগিয়ে নিলেন, যা Modules TS (২০১৮) হলো। অন্যদিকে Google-এ Clang-এর দল বাস্তবে বিশাল কোডবেসে “Clang modules” চালাচ্ছিল, আর Richard Smith-এর প্রস্তাব (ATOM নামে পরিচিত) পুরোনো header-এর সাথে মেলানোর ওপর জোর দিল। দুই নকশার মিলনেই এল header unit-এর মতো আপস, আর ২০১৯ সালের শুরুতে Kona মিটিংয়ে সেটি C++20-এ গৃহীত হলো।

কিন্তু standard-এ ঢোকাই শেষ নয়। modules-এর সবচেয়ে বড় বাধা দেখা দিল build system-এ: ফাইলগুলোর মধ্যে এখন নির্ভরতার ক্রম আছে, যা build-এর আগে scan করে বের করতে হয়। compiler আর build tool-এর মধ্যে এই তথ্য বিনিময়ের একটি মানক ফরম্যাট ঠিক করতেই কয়েক বছর লেগেছে, আর CMake আনুষ্ঠানিক সমর্থন দেয় ৩.২৮ সংস্করণে, ২০২৩ সালের শেষে।

মূল উপলব্ধি: একটি পুরোনো, বিশাল ecosystem-এ নতুন ধারণা আনার খরচ ধারণাটির জটিলতায় নয়, সামঞ্জস্যে। modules-এর পনেরো বছর আসলে পুরোনো কোড, compiler আর build tool — তিনটিকে একসাথে সরানোর সময়।

প্র ০২coroutine দুই রকম হতে পারে — stackless আর stackful. দুটির পার্থক্য কী, আর C++ কেন stackless বেছে নিল?

একটি stackful coroutine (যাকে fiber বা green thread-ও বলা হয়) নিজের একটি পুরো stack পায়, একটি ছোট thread-এর মতো। তাই সে যেকোনো গভীরতা থেকে থামতে পারে: সে একটি ফাংশন ডাকল, সেটি আরেকটি ডাকল, আর সেই গভীর ফাংশনের ভেতর থেকেও পুরো চেইনটি একসাথে থেমে যেতে পারে। Go-র goroutine, Lua-র coroutine, আর C++-এ Boost.Context-এর ওপর তৈরি লাইব্রেরি এই ধরনের। সুবিধা: সাধারণ ফাংশন বদলাতে হয় না। দাম: প্রতিটি coroutine-এর জন্য একটি stack — সাধারণত কয়েক কিলোবাইট থেকে শুরু — আর stack বড় হওয়ার প্রয়োজন সামলানোর ঝামেলা। লাখ লাখ coroutine হলে মেমরির হিসাব ভারী হয়ে যায়।

একটি stackless coroutine শুধু নিজের অবস্থাটুকু রাখে — নিজের frame, ঠিক যতটুকু দরকার। তাই সে কেবল নিজের শরীরের ভেতরেই থামতে পারে; সে যে সাধারণ ফাংশন ডাকে, সেগুলোর ভেতর থেকে থামা যায় না। গভীরে থামতে চাইলে মাঝের প্রতিটি ফাংশনকেও coroutine হতে হয় আর co_await করতে হয় — এই “ছোঁয়াচে” স্বভাবকে অনেকে “function coloring” সমস্যা বলেন। C#, JavaScript আর Python-এর async/await-ও এই ধরনের।

Microsoft-এর Gor Nishanov-এর নেতৃত্বে C++ stackless পথ নিল, কয়েকটি কারণে। প্রথমত, zero-overhead নীতি: frame-এর আকার compiler জানে, তাই অকারণে বড় stack নিতে হয় না, আর লাখ লাখ coroutine সস্তায় রাখা যায়। দ্বিতীয়ত, অপ্টিমাইজেশন: compiler পুরো coroutine দেখতে পায়, তাই কিছু ক্ষেত্রে heap বরাদ্দ মুছে দিতে বা inline করতে পারে। তৃতীয়ত, stack বদলানোর মতো নিচু স্তরের কৌশল প্রতিটি প্ল্যাটফর্মে আলাদা আর কিছু পরিবেশে (যেমন কিছু embedded বা নিরাপত্তা-সীমিত সিস্টেমে) ঝামেলার। বিতর্কও কম হয়নি — Google-এর প্রকৌশলীরা “Core Coroutines” নামে একটি বিকল্প প্রস্তাব দেন, আর Asio-র লেখক Christopher Kohlhoff আরেকটি। শেষে Nishanov-এর নকশা, কিছু পরিমার্জনসহ, ২০১৯ সালে C++20-এ গৃহীত হয়। stackful পথের জন্য Oliver Kowalke-র fiber প্রস্তাব আলাদাভাবে আলোচিত হয়েছে।

মূল উপলব্ধি: stackful সুবিধা দেয় ব্যবহারে, stackless দেয় খরচে ও অপ্টিমাইজেশনে। C++ তার চিরকালের নীতি মেনে কম খরচের যন্ত্রটি ভাষায় রেখেছে, আর ব্যবহার সহজ করার দায় দিয়েছে লাইব্রেরিকে — যেমন std::generator.

প্র ০৩তিন বছরের ট্রেন মডেল C++-কে কী দিয়েছে, আর কী কেড়ে নিয়েছে? প্রতিবার “ছোট” standard কি খারাপ?

C++11-এর আগের অভিজ্ঞতা ছিল কষ্টের। C++98-এর পর প্রায় তেরো বছর ভাষায় বড় কোনো নতুন বৈশিষ্ট্য আসেনি (C++03 ছিল মূলত সংশোধন)। এই সময়ে Java আর C# দ্রুত এগিয়েছে, আর অনেকে ধরে নিয়েছিলেন C++ পুরোনো হয়ে যাচ্ছে। সমস্যার মূল ছিল কাঠামোয়: standard একটি নির্দিষ্ট বৈশিষ্ট্যের তালিকা শেষ হওয়ার অপেক্ষায় থাকত, আর একটি দেরি হলে সবাই আটকে যেত। C++0x-এর মূল concepts নকশা এর বড় উদাহরণ — বছরের পর বছর কাজ হলো, শেষে ২০০৯ সালে সেটি বাদ দিতে হলো, আর আধুনিক রূপে ফিরল C++20-এ।

C++11-এর পর তখনকার কমিটির convenor Herb Sutter-সহ অনেকে সময়-নির্দিষ্ট মডেলের পক্ষে দাঁড়ান: তারিখ স্থির, বৈশিষ্ট্যের তালিকা নমনীয়। এর লাভ বিশাল। compiler-নির্মাতারা জানেন কখন কী আসবে, তাই তারা আগেভাগে বাস্তবায়ন করেন — C++14 আর C++17-এর প্রায় সব বৈশিষ্ট্য প্রকাশের কাছাকাছি সময়েই বড় compiler-গুলোতে এসে গিয়েছিল। ব্যবহারকারীরা নিয়মিত, ছোট ধাপে আপগ্রেড করতে পারেন, তেরো বছরের একটি বিশাল লাফের বদলে। আর TS-এর পথ ঝুঁকির বৈশিষ্ট্যগুলোকে আগে বাস্তবে পরীক্ষা করার সুযোগ দেয়।

কিন্তু দামও আছে। বড় বৈশিষ্ট্যগুলো কয়েকটি ট্রেন মিস করে, আর কখনো কখনো “সময় হয়েছে” বলে আংশিক অবস্থায় ওঠে — C++20-এর coroutine ভাষার যন্ত্র দিল কিন্তু তৈরি লাইব্রেরি টাইপ দিল না, তাই সাধারণ ব্যবহারকারী C++23-এর std::generator পর্যন্ত অপেক্ষা করলেন। ranges-এর বেলায়ও অনেক দরকারি view (zip, enumerate) এল C++23-এ। আরেকটি দাম হলো ভাষার আকার: প্রতি তিন বছরে নতুন কিছু যোগ হয়, পুরোনো প্রায় কিছুই সরে না, ফলে শেখার পরিমাণ শুধু বাড়ে। আর compiler-এর পক্ষে সব বৈশিষ্ট্য সময়মতো বাস্তবায়ন করা কঠিন হয়ে পড়ছে, যা modules-এ স্পষ্ট দেখা গেছে।

মূল উপলব্ধি: নিয়মিত ছোট ধাপ একটি বিশাল অনিশ্চিত লাফের চেয়ে নিরাপদ — সফটওয়্যার release-এর এই শিক্ষা C++ কমিটি নিজের ওপরেই প্রয়োগ করেছে। দাম হলো বড় বৈশিষ্ট্য ভাগে ভাগে আসে, তাই কোন standard-এ কোন অংশ এসেছে, তা আলাদা করে জানতে হয়।

প্র ০৪Titus Winters-এর প্রস্তাব “ABI: Now or Never” (P1863) কী নিয়ে ছিল, আর কমিটির সিদ্ধান্ত C++-এর ভবিষ্যৎকে কীভাবে প্রভাবিত করল?

পর্ব ৩-এ দেখেছেন, ABI হলো compile হওয়া কোডের বাইনারি স্তরের নিয়ম — object-এর মেমরি বিন্যাস, ফাংশনে argument পাঠানোর পদ্ধতি, symbol-এর নাম। standard-এ ABI-র কোনো নিয়ম নেই, কিন্তু বাস্তবে compiler আর standard library-র নির্মাতারা বহু বছর ধরে ABI স্থির রাখেন, যাতে দশ বছর আগে compile করা একটি লাইব্রেরি আজকের প্রোগ্রামের সাথে link হতে পারে। এর ফলে কমিটি এমন কোনো বদল প্রায় কখনো করে না, যা বিদ্যমান টাইপের বিন্যাস বদলায়।

Google-এর Titus Winters ২০১৯ সালে P1863 প্রস্তাবে যুক্তি দেন, এই অলিখিত প্রতিশ্রুতির দাম অনেক বেড়ে গেছে। উদাহরণ হিসেবে প্রায়ই বলা হয়: std::unordered_map-এর নকশা আধুনিক hash table-এর চেয়ে ধীর, কিন্তু বদলানো যায় না; std::regex-এর বাস্তবায়ন কুখ্যাতভাবে ধীর, কিন্তু ABI-র ভয়ে সংস্কার আটকে আছে; আর Chandler Carruth দেখিয়েছিলেন, ABI-র নিয়মের কারণে std::unique_ptr প্রচলিত প্ল্যাটফর্মে register-এ পাঠানো যায় না, তাই কাঁচা pointer-এর চেয়ে একটু বেশি খরচ হয়। Winters-এর প্রস্তাবের শিরোনামই প্রশ্ন: হয় এখনই একবার ABI ভাঙার পরিকল্পনা করুন, নয়তো স্বীকার করুন যে C++ কখনো ভাঙবে না — আর সেই অনুযায়ী ভাষার পারফরম্যান্সের দাবি নতুন করে ভাবুন।

২০২০ সালের শুরুতে Prague মিটিংয়ে আলোচনার পর কমিটি C++23-এ ABI ভাঙার পক্ষে যায়নি, আবার চিরকাল না-ভাঙার স্পষ্ট প্রতিশ্রুতিও দেয়নি — কার্যত আগের অবস্থাই বহাল রইল। অনেকের মতে এই সিদ্ধান্তের একটি প্রভাব হলো Google-এর মনোযোগ সরে যাওয়া: Google-এর প্রকৌশলীরা পরে ২০২২ সালে Carbon নামে একটি পরীক্ষামূলক “উত্তরসূরি” ভাষা ঘোষণা করেন। অন্যদিকে স্থিতিশীলতার পক্ষের যুক্তিও শক্ত — ব্যাংক, টেলিকম বা সরকারি সিস্টেমে এমন বাইনারি লাইব্রেরি চলে, যার source আর নেই; ABI ভাঙা মানে তাদের জন্য বিশাল খরচ।

মূল উপলব্ধি: পুরোনো কোড না ভাঙার প্রতিশ্রুতি C++-এর সবচেয়ে বড় শক্তি, আবার সবচেয়ে বড় শৃঙ্খলও। কোনো বৈশিষ্ট্য “কেন এত অদ্ভুত” মনে হলে প্রায়ই উত্তর হলো — ভালোটা করা যেত, কিন্তু তাতে কারও পুরোনো বাইনারি ভেঙে যেত।

প্র ০৫Carbon, cppfront আর Circle — তিনটি পরীক্ষা, যারা C++-এর ভবিষ্যৎ নিয়ে ভিন্ন উত্তর দেয়। এরা C++-এর বিবর্তনের সঙ্গে কীভাবে সম্পর্কিত?

তিনটি প্রকল্পই একটি প্রশ্ন থেকে আসে: C++-এর পুরোনো কোডের বোঝা না বয়ে কি ভাষাটিকে সরল আর নিরাপদ করা যায়? কিন্তু তাদের উত্তর আলাদা।

Carbon ঘোষণা করেন Google-এর Chandler Carruth, ২০২২ সালে CppNorth সম্মেলনে। এটি একটি আলাদা ভাষা, নিজের syntax আর নিজের বিবর্তন-নীতি নিয়ে, যার মূল লক্ষ্য C++-এর সাথে দুই দিকে চলার মতো সামঞ্জস্য — একটি Carbon ফাইল C++ header ব্যবহার করতে পারবে, আর C++ কোড Carbon-কে ডাকতে পারবে। ধারণাটি অনেকটা Kotlin-Java বা TypeScript-JavaScript-এর মতো: পুরোনো কোড ধীরে ধীরে নতুন ভাষায় সরানো। প্রকল্পটি নিজেই নিজেকে পরীক্ষামূলক বলে।

cppfront লিখেছেন Herb Sutter, যিনি বহু বছর C++ কমিটির convenor ছিলেন; এটি ২০২২ সালের CppCon-এ সামনে আসে। এখানে একটি নতুন, সরল syntax (“Cpp2”) সাধারণ C++-এ অনুবাদ হয়, তারপর যেকোনো C++ compiler তা compile করে। Sutter এটিকে আলাদা ভাষা হিসেবে নয়, বরং C++-এর জন্য ধারণা পরীক্ষার একটি গবেষণাগার হিসেবে উপস্থাপন করেন — এখানে যা কাজ করে, তা প্রস্তাব আকারে কমিটিতে নেওয়ার লক্ষ্যে। reflection-এর ওপর তৈরি metafunction-এর মতো ধারণা এখানে পরীক্ষা হয়েছে।

Circle হলো Sean Baxter-এর একক হাতে তৈরি একটি C++ compiler, যাতে বহু পরীক্ষামূলক বৈশিষ্ট্য আছে — শক্তিশালী compile-time কোড চালানো থেকে শুরু করে Rust-এর মতো borrow checking. এই অভিজ্ঞতা থেকে ২০২৪ সালে তিনি “Safe C++” নামে একটি প্রস্তাব দেন। কমিটি তখন সেই পথে যায়নি; বরং Bjarne Stroustrup-সহ অনেকে “profiles” ধারণা — নির্দিষ্ট নিয়ম মানলে নির্দিষ্ট ধরনের ভুল নিষিদ্ধ করা — নিয়ে কাজ চালিয়ে যাচ্ছেন, যা এখনো প্রস্তাব পর্যায়ে।

তিনটিরই প্রভাব আছে C++-এর ওপর, এমনকি C++ এদের কোনোটিকেই সরাসরি গ্রহণ না করলেও। এরা প্রমাণ দেয় কোন ধারণা বাস্তবে কাজ করে, আর memory safety নিয়ে সরকারি ও শিল্প-পর্যায়ের চাপের মুখে কমিটিকে দ্রুত সিদ্ধান্ত নিতে ঠেলে দেয় — যার একটি ফল C++26-এর erroneous behavior আর hardened library-র মতো পদক্ষেপ।

মূল উপলব্ধি: একটি পরিণত ভাষা বিবর্তিত হয় ভেতরের প্রস্তাব আর বাইরের প্রতিযোগিতা — দুই দিক থেকে। আপনার জন্য ব্যবহারিক শিক্ষা: আজকের C++ ভালোভাবে শিখুন, কারণ এই পরীক্ষাগুলোর সবই সেই জ্ঞানের ওপরেই দাঁড়িয়ে আছে।

অনুশীলন

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

  1. আউটপুট অনুমান: উপরের invoice_gen.cpp-এ দুটি বদল করুন — println(" [generator] making ...") লাইনটি co_yield-এর পরে সরান, আর break-এর শর্ত == 2 করুন। এখন আউটপুট কয় লাইন, আর কোন কোন লাইন?

    তিন লাইন: caller got INV-1001, [generator] making 1001, caller got INV-1002. প্রথম মান চাইলে generator সরাসরি co_yield 1001-এ পৌঁছে থামে, কিছু ছাপে না। দ্বিতীয় মান চাইলে সে co_yield-এর পর থেকে জাগে, তাই আগে “making 1001” ছাপে (n তখনো 1001), তারপর ++n করে 1002 দেয়। দ্বিতীয়টির পর caller break করে, তাই generator আর জাগে না — “making 1002” কখনো ছাপা হয় না। থামার জায়গাটিই ঠিক করে কোন কাজ কখন হবে।

  2. সীমানা ভাবুন: tax.h-এ লেখা আছে #define RATE 15 আর একটি ফাংশন। একই জিনিস tax.cppm-এ একটি module হিসেবে লেখা হলো (export module tax;), যেখানে ফাংশনটি export করা, macro-টি নয়। যে ফাইল #include "tax.h" করে আর যে ফাইল import tax; করে — দুজনের কে RATE দেখতে পায়? আর দুই ফাইলের কোনটির আগে #define RATE 20 লিখলে tax-এর আচরণ বদলে যেতে পারে?

    #include করা ফাইল RATE দেখে, কারণ header-এর পুরো লেখা তার ভেতরে কপি হয়েছে। import করা ফাইল দেখে না — module থেকে macro বের হয় না, শুধু export করা নাম আসে। দ্বিতীয় প্রশ্নে: #include-এর আগে #define RATE 20 লিখলে header-এর লেখা সেই macro-র প্রভাবে পড়তে পারে (header-এর #define তখন redefinition warning দেবে, বা #ifndef RATE থাকলে চুপচাপ 20 নেবে) — অর্থাৎ ব্যবহারকারীর macro header-এর অর্থ বদলাতে পারে। import-এর আগে লেখা macro module-এর ভেতরে ঢোকে না, কারণ module আগেই আলাদাভাবে compile হয়েছে। সীমানা দুই দিকেই বন্ধ।

  3. AI-এর কোড বিচার: একটি AI বলল, “এটি C++20, যেকোনো আধুনিক compiler-এ চলবে” — আর দিল: import std;, একটি ফাংশন std::generator<int> evens(const std::vector<int>& v), ভেতরে if (s.contains("x")) (s একটি std::string), আর ডাকার জায়গায় evens(std::vector{1, 2, 3, 4}). দাবিটির কোথায় কোথায় ভুল?

    চারটি সমস্যা। (১) import std; C++23, আর বেশিরভাগ toolchain-এ বাড়তি সেটিংও লাগে — “যেকোনো compiler-এ” চলবে না। (২) std::generator C++23, C++20 নয়। (৩) std::string::contains C++23; C++20-এ আছে শুধু starts_with/ends_with. (৪) সবচেয়ে বিপজ্জনক: coroutine-এর প্যারামিটার const std::vector<int>&, আর ডাকা হয়েছে একটি অস্থায়ী vector দিয়ে। generator অলস — সে চলবে পরে, লুপের সময়, কিন্তু অস্থায়ী vector ততক্ষণে ধ্বংস হতে পারে (যেমন generator-টি একটি ভ্যারিয়েবলে রেখে পরে লুপ করলে)। প্যারামিটার by value নিন: std::vector<int> v.

  4. নিজে লিখুন (কোড): একটি std::generator<int> roll_numbers(int from, int to) লিখুন, যা from থেকে to পর্যন্ত শুধু জোড় রোল নম্বর দেয়। main-এ roll_numbers(1, 10)-এর সব মান এক লাইনে, ফাঁকা জায়গা দিয়ে ছাপুন। আউটপুট আগে অনুমান করুন।

    একটি সমাধান: std::generator<int> roll_numbers(int from, int to) { for (int r = from; r <= to; ++r) if (r % 2 == 0) co_yield r; } আর main-এ for (int r : roll_numbers(1, 10)) std::print("{} ", r); std::println("");. আউটপুট: 2 4 6 8 10 . এখানে লুপ শেষ হলে coroutine নিজেই শেষ হয় — আলাদা co_return লিখতে হয় না, শরীরের শেষে পৌঁছানোই যথেষ্ট। চাইলে পর্ব ৩৫-এর মতো জুড়ে দেখুন: roll_numbers(1, 100) | std::views::take(3) — প্রথম তিনটি জোড় সংখ্যার পর আর কিছু তৈরি হয় না।

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

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

ক্রমএই C++ standard-গুলো প্রকাশের ক্রমে সাজান — সবচেয়ে পুরোনোটি ওপরে (▲▼ চেপে সরান)

  1. C++98 — প্রথম ISO standard, STL
  2. C++11 — auto, lambda, move, smart pointer
  3. C++14 — generic lambda, make_unique
  4. C++17 — optional, variant, filesystem
  5. C++20 — concepts, ranges, modules, coroutines
  6. C++23 — print, expected, generator, import std

C++11-এর পর থেকে ISO C++ standard সাধারণত কত বছর পরপর প্রকাশিত হয়?

  • প্রতি বছর
  • প্রতি দুই বছরে
  • প্রতি তিন বছরে
  • কোনো নির্দিষ্ট সময় নেই, বৈশিষ্ট্য তৈরি হলে

C++20 module-এর interface ফাইলে #define LIMIT 100 লেখা আছে। যে ফাইল সেই module import করে, সে কি LIMIT ব্যবহার করতে পারে?

  • হ্যাঁ, #include-এর মতোই macro চলে আসে
  • না, module থেকে macro বাইরে আসে না
  • হ্যাঁ, যদি macro-র সামনে export লেখা থাকে
  • না, কারণ module-এ macro লেখাই compile error

C++-এ কোন বৈশিষ্ট্য থাকলে একটি ফাংশনকে coroutine বলা হয়?

  • ফাংশনের নামের আগে coroutine শব্দটি থাকলে
  • ফেরত-টাইপ std::generator হলেই, শরীর যা-ই হোক
  • ফাংশনটি নিজেকে ডাকলে (recursion)
  • শরীরে co_await, co_yield বা co_return থাকলে

একটি C++23 std::generator<int> 1, 2, 3… একটি অসীম লুপে co_yield করে। ডাকা-পক্ষ range-for দিয়ে তিনটি মান নিয়ে break করল। generator মোট কয়টি মান তৈরি করেছে?

  • ৩টি
  • ৪টি — একটি আগে থেকে তৈরি থাকে
  • অসীম — প্রোগ্রাম কখনো শেষ হয় না
  • ০টি — ডাকার সময় generator চলে না

C++20-এ void log(std::string_view m, std::source_location at = std::source_location::current()) — main-এর ২০ নম্বর লাইন থেকে log("x") ডাকলে at.line() কী দেবে?

  • log ফাংশনটি যে লাইনে লেখা, সেটি
  • সবসময় 0, কারণ মানটি চলার সময় জানা যায় না
  • 20 — যে লাইন থেকে log ডাকা হয়েছে
  • compile error — default argument-এ ফাংশন ডাকা যায় না

C++ কোডে compile-এর সময় জানতে হবে, standard library std::generator সমর্থন করে কি না। সবচেয়ে নির্ভরযোগ্য উপায় কোনটি?

  • __cplusplus >= 202302L পরীক্ষা করা
  • <version> include করে __cpp_lib_generator পরীক্ষা করা
  • compiler-এর নাম আর সংস্করণ দেখে অনুমান করা
  • সরাসরি #include <generator> লিখে দেখা

শব্দকোষ

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

মনে রাখুন

  1. ট্রেন মডেল: C++11-এর পর প্রতি তিন বছরে একটি standard; C++20 সবচেয়ে বড় বদল (concepts, ranges, modules, coroutines), C++23 তাকে পূর্ণ করেছে।
  2. Modules: export module দিয়ে interface, import দিয়ে ব্যবহার — লেখা কপি নয়, compile করা interface; macro ফাঁস হয় না। toolchain-এর সমর্থন এখনো উন্নত হচ্ছে।
  3. Coroutine: থেমে আবার চলা ফাংশন (co_yield, co_await, co_return); অবস্থা থাকে coroutine frame-এ। সহজ শুরু std::generator; প্যারামিটার by value.
  4. ছোট সুবিধা: source_location (macro-বিহীন log), span, contains/starts_with, bit_cast, using enum, ssize; C++23-এ m[i, j], mdspan, flat_map.
  5. যাচাই আগে: compiler support table আর feature-test macro (<version>) দেখে তবেই নতুন বৈশিষ্ট্য — AI-এর “এটি C++20” দাবি মিলিয়ে নিন।

পরের পর্বে: নতুন বৈশিষ্ট্য যত সুন্দরই হোক, C++-এর সবচেয়ে বড় বিপদ রয়ে গেছে পুরোনো জায়গায় — undefined behavior. কোন কোন ভুল UB, কেন compiler সেগুলো ধরে না, আর sanitizer, hardened library ও নিরাপদ অভ্যাস দিয়ে কীভাবে নিজেকে রক্ষা করবেন।

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

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