5.2.32 · D3 · Coding › C++ Programming › Modules (C++20) — concept and syntax
Intuition Yeh page kya hai
Parent note ne rules sikhaye. Yahan hum un rules ko har us situation mein run karte hain jo ek C++ module file mein ho sakti hai — export ki har position, #include ke har legal aur illegal spot, single-file vs split, partitions, standard library, aur woh traps jo exams mein common hain. Har example pehle aapko forecast karne kehti hai, phir reasoning walk karte hain, phir compiler kya dekhta hai trace karke verify karte hain.
Prerequisite ideas jo hum yahan use karte hain: The C++ Preprocessor (#include asal mein kya karta hai), Translation Units and Linkage (ek "compiled unit" kya hota hai), Namespaces in C++ (visibility ka ek alag axis), Module Partitions (ek module ko multiple files mein split karna), aur Build Systems and Compilation Speed (yeh sab kyun matter karta hai).
Do phrases neeche har example mein milte hain — global module fragment aur named-module region — toh chalo inhe ek picture mein pin karte hain pehle hum inhe use karein. Woh line jo fragment kholti hai woh bare word module; hai — ek token, ek semicolon, kuch nahi — toh use pehle define karte hain.
module; marker
module; (word module immediately followed by semicolon, bina kisi name ke ) ek module declaration nahi hai — yeh global module fragment ka opening hai. Yeh kehta hai: "jo lines iske baad hain, export module Name; tak, woh airlock hai jahan legacy preprocessor code rehta hai." Figure mein yeh top plum bar hai.
Yeh required kyun hai? Bina module; line ke koi fragment nahi hoga, aur phir #include likhne ki koi legal jagah nahi — kyunki #include named-module region ke andar banned hai (orange divider ke neeche). Toh agar aapko ek bhi legacy header chahiye, aapko module; se open karna hi padega .
Agar aap ise omit kar dein? Tab aapki file seedha export module Name; se shuru honi chahiye aur usme zero #include directives hone chahiye. Bina preceding module; ke #include add karna compile error hai (E3 yeh dikhata hai).
Dhyan se: module; (bare) ≠ module Name; (jo ek implementation unit hai, E4) ≠ export module Name; (jo interface unit hai). Teen alag lines, teen alag kaam.
Definition Ek module file ke do regions
Ek module file ko top-to-bottom padho. Iske (zyada se zyada) do zones hote hain, export module Name; line se separate hoke:
Global module fragment — module; aur export module Name; ke beech ka optional band. Yeh ek quarantine zone hai: woh ek hi jagah jahan legacy #include reh sakta hai. Socho isse airlock ki tarah jahan The C++ Preprocessor ka textual paste contain hota hai.
Named-module region — export module Name; se aage sab kuch. Yeh clean, compiled duniya hai. Yahan koi #include allowed nahi. Yahan jo names aap likhte hain woh module se belong karte hain aur export ho sakte hain.
#include ko quarantine kyun karte hain? #include textual paste hai (dekho The C++ Preprocessor ): yeh compilation se pehle kisi doosri file ka raw text — aur uske macros — aapki file mein dump kar deta hai. Standard require karta hai ki yeh paste named-module region khulne se pehle ho, taaki compiled module interface clean rahe: module mein jo names hain woh module se attached hain, lekin pasted header text global module se belong karta hai (woh nameless bucket jo fragment feed karta hai), toh woh kabhi importers ko dikhne waale hisse ka part nahi banta. Quarantine = "purana messy paste hone do, lekin use naye clean interface se wall off kar do."
Agar aap koi legacy headers use nahi karte, toh fragment skip kar dete hain aur seedha export module Name; se start karte hain (E1 mein degenerate case).
Figure mein plum-shaded band (fragment / airlock, module; se khula) aur dividing line ke neeche teal region (named module) follow karo. Baad ke har example mein bas yahi variation hai ki ek line us dividing line ke relative kahan padti hai.
Yeh pura space hai un cases ka jo module topic aap par throw kar sakta hai. Neeche har cell mein kam se kam ek worked example hai — example numbers brackets mein hain.
Axis
Case class
Covered by
Export position
single export on one declaration
E1
export { ... } block (group)
E1
no export → private name
E1, E2
#include placement
legal: inside global module fragment
E3
illegal: after export module
E3
degenerate: no fragment needed at all
E1
File roles
one file does everything (interface = implementation)
E1
split: interface unit + implementation unit
E4
Boundary confusion
export vs class public/private
E5
Multiplicity
do files same export module claim karte hain (error)
E6
correct fix: partitions
E6
Macros
macro leakage: kya import use carry karta hai? (no)
E7
header unit import "h.h" does carry it
E7
Standard library
import std; / header units vs #include
E7
Real-world word problem
build-speed estimate: parse-once payoff
E8
Exam twist
trace karo kya compiles / kya kahan visible hai
E2, E5, E9
main.cpp kaun se names call kar sakta hai?
// math.ixx (primary interface unit)
export module math;
export int add ( int a, int b) { return a + b; } // single export
export { // group export
int sub ( int a, int b) { return a - b; }
int mul ( int a, int b) { return a * b; }
}
int helper ( int x ) { return x * x; } // no export -> private
// main.cpp
import math;
int main () { return add ( 1 , 2 ) + sub ( 9 , 4 ) + mul ( 2 , 5 ); } // returns ?
Forecast: kaun se chaar names math mein exist karte hain, aur kaun se teen main mein visible hain? main kaun sa integer return karta hai?
Step 1 — har woh name list karo jo module define karta hai. add, sub, mul, helper.
Yeh step kyun? Definition aur export do alag sawaal hain; pehle inhe alag karo warna confuse ho jaoge.
Step 2 — mark karo kaun boundary cross karta hai. add (single export), sub aur mul (export {} group ke andar). helper mein koi export nahi → woh module mein private rehta hai. Figure mein, teen exported names module wall ke bahar hain; helper andar trapped hai.
Yeh step kyun? export keyword hi gate hai; group block ka meaning bilkul wahi hai jaise har line par export likhna — sirf convenience hai.
Step 3 — #include ki zaroorat check karo. Koi nahi. Koi header use nahi, toh koi global module fragment nahi — degenerate, cleanest case. File seedha export module math; se start ho sakti hai, bina module; line ke.
Yeh step kyun? Aap kabhi "just in case" mein module; fragment nahi add karte — sirf tab jab koi legacy #include demand kare.
Step 4 — return compute karo. a dd ( 1 , 2 ) = 3 , s u b ( 9 , 4 ) = 5 , m u l ( 2 , 5 ) = 10 , total 3 + 5 + 10 = 18 .
Yeh step kyun? main sirf exported names touch karta hai, toh compile hoga; ab arithmetic trivial hai.
Verify: helper kabhi main se call nahi hota, toh koi visibility error nahi. Sum 3 + 5 + 10 = 18 . ✅
Worked example Kya yeh compile hoga?
Same math.ixx jaisa E1 mein. Ab:
import math;
int main () { return helper ( 4 ); } // ?
Forecast: compile hoga ya error?
Step 1 — kya helper exported hai? Nahi — E1 ne establish kiya ki usme koi export nahi.
Yeh step kyun? Module boundary ke across visibility sirf aur sirf export se decide hoti hai, kuch aur nahi.
Step 2 — import math; kya laata hai? Exactly exported surface : add, sub, mul. helper nahi (yaad hai woh E1 figure mein module wall ke andar trapped tha).
Yeh step kyun? Yahi toh modules ka poora selling point hai — private interior invisible hai, koi leakage nahi.
Step 3 — conclusion. helper main.cpp mein ek undeclared name hai → compile error ("helper was not declared in this scope").
Verify: Agar main koi exported name jaise add(4,4) call karta, toh compile hota aur 8 return karta. Sirf helper fail karta hai. ✅
Worked example Do versions — ek compile hota hai, ek reject hota hai. Kaun sa?
Version A
module ; // global module fragment BEGINS
#include <cmath> // legacy header — allowed here
export module geometry; // named-module region begins now
export double hyp ( double a, double b) { return std :: sqrt (a * a + b * b); }
Version B
export module geometry;
#include <cmath> // <-- after the named module started
export double hyp ( double a, double b) { return std :: sqrt (a * a + b * b); }
Forecast: compiler kaun sa accept karta hai?
Step 1 — #include locate karo. A mein woh module; aur export module geometry; ke beech hai — global module fragment , figure mein left side ka plum-shaded airlock. B mein woh export module geometry; divider ke neeche hai — teal named-module region ke andar.
Yeh step kyun? Ek hi rule hai position ka: #include sirf fragment mein legal hai (upar ki anatomy diagram yaad karo).
Step 2 — rule apply karo. A legal hai (iska module; line ne fragment khola #include ke liye). B standard violate karta hai: named module shuru hone ke baad koi #include nahi — aur B ne koi fragment nahi khola kyunki iska koi module; line hi nahi.
Yeh step kyun? #include The C++ Preprocessor ka textual paste hai; yeh paste module ke clean, compiled region shuru hone se pehle hona chahiye, warna use re-pollute kar deta.
Step 3 — hyp par numeric sanity. a = 3 , b = 4 ke saath: 3 2 + 4 2 = 9 + 16 = 25 = 5 .
Yeh step kyun? Confirm karta hai ki file compile hone ke baad hyp Euclidean length (right-triangle hypotenuse) compute karta hai — placement rule se independent ek value check.
Verify: Version A: h y p ( 3 , 4 ) = 5 . Version B: kabhi run hone se pehle rejected. ✅
Worked example Har definition kahan rehti hai, aur kya
main bal_ dekhta hai?
// account.ixx (interface unit)
export module account;
export class Account {
public:
Account ( double b );
void deposit ( double a );
double balance () const ;
private:
double bal_;
};
// account.cpp (implementation unit — NO export)
module account; // note: bare 'module Name'
Account :: Account ( double b) : bal_ (b) {}
void Account :: deposit ( double a ) { bal_ += a; }
double Account :: balance () const { return bal_; }
// main.cpp
import account;
int main () {
Account acc ( 100.0 );
acc. deposit ( 25.0 );
return static_cast<int> (acc. balance ()); // ?
}
Forecast: kaun sa integer print hota hai, aur kya main seedha acc.bal_ read kar sakta hai?
Step 1 — har file ka role. account.ixx = woh ek primary interface unit (export module). account.cpp = implementation unit (module account; — named , koi export nahi; contrast with bare, nameless module; fragment line E3 se). Yeh definitions add karta hai lekin koi naya public name nahi . Figure mein interface file public face feed karti hai, implementation file sirf bodies feed karti hai.
Yeh step kyun? Bodies ko interface se bahar rakhne ka matlab hai ki koi body change karne par sirf account.cpp recompile hoga, har importer nahi — Build Systems and Compilation Speed ka payoff.
Step 2 — main kya call kar sakta hai. Account ka constructor, deposit, balance public hain aur class exported hai → visible. bal_ private hai → module status chahey jo ho, class level par block.
Yeh step kyun? Do axes hain: export ne decide kiya ki class module boundary cross kare; public/private decide karta hai member access cross karne ke baad .
Step 3 — balance trace karo. Start 100.0 ; deposit(25.0) → 100.0 + 25.0 = 125.0 ; cast to int → 125 .
Yeh step kyun? Actual number jo program return karta hai uska sanity-check.
Verify: Return value = 125 . Direct acc.bal_ access → compile error (private member). ✅
Worked example Chaar combinations — kaun se outside se usable hain?
export module widget;
export class W { // class W crosses the module boundary
public: int a (); // (1) exported class + public
private: int b (); // (2) exported class + private
};
class Hidden { // NOT exported
public: int c (); // (3) private class + public member
};
Forecast: W::a, W::b, Hidden::c mein se kaun sa importer use kar sakta hai?
Step 1 — do independent axes. export = "kya yeh file-level name module se bahar jaata hai?" public/private = "class ke kaun se members callers touch kar sakte hain?" Yeh multiply karte hain, overlap nahi. Figure inhe series mein do doors draw karta hai.
Yeh step kyun? Classic exam trap hai export ko public ka synonym maanana. Yeh nahi hai — yeh alag sawaalon ke jawab dete hain.
Step 2 — har cell evaluate karo.
(1) W::a — class exported ✅ aur member public ✅ → usable .
(2) W::b — class exported ✅ lekin member private ✗ → not usable (class access ne block kiya).
(3) Hidden::c — class not exported ✗ → poori class importers ko invisible hai, toh c unreachable hai → not usable .
Yeh step kyun? Dono gates open hone chahiye. Koi ek fail karo aur name andar hi reh jaayega.
Step 3 — usable names count karo. Exactly 1 teen mein se (W::a).
Yeh step kyun? Trace ko ek single count mein reduce karna woh jawab hai jo exam actually grade karta hai — aur yeh force karta hai ki AND-of-two-doors rule har row par apply ho, sirf ek ko eyeball karne ki jagah.
Verify: Truth-table view — usable ⇔ (class exported) AND (member public). Sirf row (1) true AND true hai. ✅
Ek name bahar tab pahunchta hai jab woh dono doors pass kare: module door (export) aur class door (public). Koi ek miss karo → andar hi stuck.
Worked example "Ek module, do interface files" error fix karo.
// shape_a.ixx
export module shape; // primary interface unit #1
// shape_b.ixx
export module shape; // primary interface unit #2 <-- illegal
Forecast: error kya hai, aur correct multi-file layout kya hoga?
Step 1 — primary interface units count karo. Ek module mein exactly ek file hoti hai jo export module Name; (bina colon ke) likhti hai. Yahan do files yahi kar rahi hain → compiler/linker duplicate primary interface unit reject karta hai.
Yeh step kyun? Primary interface module ki single "public face" hai; do faces hoti toh import shape; ambiguous ho jaata.
Step 2 — correct pattern: partitions. Public surface ko named partitions mein split karo, har ek export module shape:name;, phir unhe ek hi primary unit se re-export karo:
// shape-circle.ixx (interface PARTITION)
export module shape:circle;
export double area_circle ( double r);
// shape-rect.ixx (interface PARTITION)
export module shape:rect;
export double area_rect ( double w, double h);
// shape.ixx (THE single primary interface unit)
export module shape;
export import :circle; // pull partition into the public face
export import :rect;
Yeh step kyun? Colon : partition name karta hai; export import :circle; use re-export karta hai taaki shape ke importers sab kuch dekhein. Exactly ek colon-less export module shape; rehta hai. Dekho Module Partitions . Figure mein left side do illegal peers hain aur right side correct hub-and-partitions layout hai.
Step 3 — numeric sanity. Partitions ko bodies do a r e a _ c i r c l e ( r ) = π r 2 aur a r e a _ r ec t ( w , h ) = w h . Toh a r e a _ r ec t ( 3 , 4 ) = 12 aur a r e a _ c i r c l e ( 2 ) = 4 π ≈ 12.566 .
Yeh step kyun? Ek layout legally correct ho sakta hai lekin phir bhi galat compute kare; formulas check karna confirm karta hai ki partitioned code sirf structurally valid nahi balki numerically bhi correct hai.
Verify: Ek colon-less unit → legal. a r e a _ r ec t ( 3 , 4 ) = 12 exactly; a r e a _ c i r c l e ( 2 ) = 4 π . ✅
MAX leak hota hai? Aur ek header unit #include aur module import se kaise alag hai?
// config.h
#define MAX 999
// util.ixx
module ;
#include "config.h" // MAX exists inside the fragment
export module util;
export int cap ( int x) { return x > MAX ? MAX : x; } // fragment sees MAX
// main.cpp
import util; // (A) named-module import
int main () {
int y = cap ( 1500 ); // kya yeh kaam karta hai?
int z = MAX; // (B) kya MAX yahan visible hai?
return y; // ?
}
Forecast: y ki value kya hai; kya line (B) import util; ke baad legal hai?
Step 1 — cap ka MAX use. MAX util.ixx ke global module fragment mein (module; line se khola) #include "config.h" se aaya, toh cap ka body usne legitimately use kiya util.ixx compile hote waqt . Yeh theek hai.
Yeh step kyun? Macro substitution compilation se pehle hoti hai, poori fragment ke andar — kuch bhi out compiled interface mein leak nahi hota.
Step 2 — kya import util; MAX ko main.cpp mein carry karta hai? Nahi. Ek named-module import (import util;) deliberately koi macros export nahi karta. Toh line (B) int z = MAX; → MAX undeclared → error . Figure macro ko module boundary par block hote dikhata hai.
Yeh step kyun? Macro leakage ko kill karna ek feature hai, bug nahi — yeh parent note ki problem #2 fix karta hai.
Step 3 — config.h pull karne ke teen tarike, contrasted.
Mechanism
Syntax
Har use par re-parsed?
Macros carry karta hai (MAX)?
Legacy include
#include "config.h"
Yes (textual paste, har TU mein)
Yes
Header unit
import "config.h";
No (ek baar parse, cached)
Yes
Named module
import util;
No
No
Ek header unit (import "config.h";) middle ground hai: yeh module ki tarah import hota hai (ek baar parse, fast) lekin yeh header ke macros preserve karta hai. Toh agar main.cpp ko sachchi mein MAX chahiye, woh import "config.h"; (header unit) likhega, import util; nahi .
Yeh step kyun? Exactly yahi case hai jo matrix "Macros" aur "Standard library" ke andar promise karta hai: ek header unit speed aur macro visibility deta hai, dono extremes ke unlike.
Step 4 — standard library. C++23 feature import std; poori standard library ko ek module ke roop mein expose karta hai (fast, koi macro leakage nahi). Kuch toolchains import <iostream>; ko header unit ki tarah bhi allow karte hain. Lekin note: importable standard-library headers aur import std; optional/implementation-defined hain — inhe rely karne se pehle apna compiler check karo.
Yeh step kyun? Common myth correct karta hai ki import <iostream>; portable C++20 given hai — woh nahi hai.
Step 5 — y compute karo. cap(1500): kyunki 1500 > 999 , return 999 . Toh y = 999.
Yeh step kyun? Macro discussion visibility ke baare mein hai; hum reader ko actual runtime value bhi dene wale hain, jo cap ki clamping logic par depend karta hai, MAX kahan se aaya is par nahi.
Verify: y = 999 (MAX par clamp). Line (A) import util; compile hoti hai; line (B) int z = MAX; error deta hai (named-module import koi macros export nahi karta); header unit import "config.h"; use karne se MAX visible ho jaata. ✅
Worked example Modules kitna parsing save karte hain?
Ek header bigheader.h ko parse hone mein 0.5 s lagta hai. Yeh 200 translation units mein #included hai. #include vs module import bigheader; (ek baar parse, phir reuse) mein total header-parse time compare karo.
Forecast: reading se pehle do totals aur ratio guess karo.
Step 1 — #include cost. Textual paste har translation unit mein header re-parse karta hai: 200 × 0.5 s = 100 s .
Yeh step kyun? Yeh exactly parent ke comparison table ki "re-parsed every time" row hai — quantified.
Step 2 — module cost. Module ki interface ek baar parse/compile hoti hai binary interface mein, phir reuse: 1 × 0.5 s = 0.5 s parsing (baaki 199 units sirf cached interface se link karte hain).
Yeh step kyun? "Parsed once" core mechanism hai; 199 reuses ko parse-wise ~free treat karte hain.
Step 3 — speedup ratio. 0.5 100 = 200 × .
Yeh step kyun? Abstract win ko ek number mein turn karta hai jo ek build engineer quote kar sake — dekho Build Systems and Compilation Speed .
Verify: #include: 100 s. import: 0.5 s. Ratio = 200 , translation units ki number ke barabar (jaise expect tha — aap parse N times ki jagah ek baar karte ho). ✅
Worked example Har line mark karo ✅ compiles / ✗ error.
// lib.ixx
export module lib;
export int pub () { return 7 ; }
int priv () { return 8 ; }
export { int gp () { return 9 ; } }
// client.cpp
import lib;
int main () {
int a = pub (); // L1
int b = priv (); // L2
int c = gp (); // L3
return a + c; // sirf compiling names use karta hai
}
Forecast: L1, L2, L3 mein se kaun compile hote hain, aur a + c kya equal hai?
Step 1 — har name classify karo. pub exported (single), gp exported (group block), priv not exported.
Yeh step kyun? Same single gate jaise har example mein — kya export present hai?
Step 2 — lines mark karo. L1 pub() ✅ ; L2 priv() ✗ (undeclared, exported nahi); L3 gp() ✅.
Yeh step kyun? Sirf exported names client.cpp tak pahunchte hain.
Step 3 — agar L2 remove kar dein, a + c = 7 + 9 = 16.
Yeh step kyun? Surviving, compiling program ki arithmetic confirm karta hai.
Verify: a = 7 , c = 9 , a + c = 16 . File build hone ke liye L2 delete karna padega. ✅
Recall Expand karne se pehle jawab do
Bare module; line (bina name ke) kis kaam aati hai, aur ise omit karne par kya toot jaata hai? ::: Yeh global module fragment kholti hai; ise omit karo aur #include likhne ki koi legal jagah nahi rehti (E1, E3).
Module file mein #include ka SINGLE legal spot kahan hai? ::: Global module fragment mein, module; aur export module Name; ke beech (E3).
Ek exported class ka private member — importers ko visible? ::: Nahi; class door (public) use block karta hai chahe module door (export) open ho (E5).
Kya import lib; lib ke macros laata hai? ::: Nahi — named-module import koi macros export nahi karta; agar genuinely zaroorat ho toh header unit import "h.h"; use karo (E7).
Header unit vs named-module import — kaun macros rakhta hai? ::: Header unit (import "h.h";) macros rakhta hai; named-module import (import util;) nahi rakhta (E7).
Do files dono export module shape; likhte hain — legal? ::: Nahi; ek module mein exactly ek primary interface unit hoti hai. Partitions use karo export module shape:part; (E6).
Ek header 0.5 s parse, 200 units mein include — #include vs module parse totals? ::: 100 s vs 0.5 s, 200 × saving (E8).
Mnemonic Poora page ek line mein
Ek name module ke bahar usable hai ⇔ usne module door (export) pass kiya aur , agar woh class member hai, class door (public) bhi — aur #include sirf kabhi bhi bare module; line se khule quarantine band mein rehta hai.