Exercises — Concepts (C++20) — constraining templates
5.2.18 · D4· Coding › C++ Programming › Concepts (C++20) — constraining templates
Yeh page ek ladder hai. Har rung ek level ki thinking hai, "kya tum ek concept spot kar sakte ho?" se lekar "kya tum ek concept library design kar sakte ho?" tak. Har problem ka full solution ek collapsible callout mein chhupa hua hai — problem padho, apne dimag mein ya kaagaz par try karo, phir solution kholo. Har level ke baad ek [!mistake] callout hai jo us level par log jo common galti karte hain use steel-man karta hai: kyun galat answer sahi lagta hai, aur uska fix kya hai.
Yahan sab kuch parent note par based hai: Concepts (C++20) — constraining templates. Agar koi term unfamiliar lage, toh woh wahan ya in jagahon par define ki gayi thi: Templates — function & class templates, SFINAE and std::enable_if, type_traits — std::integral, std::floating_point, Overload Resolution & Subsumption, constexpr — compile-time evaluation, Ranges library (C++20), aur auto and decltype.
Pehli exercise se pehle, neeche di gayi figure padho — yeh is page ki har problem ke peeche ki picture hai.

Level 1 — Recognition
Tumse sirf read karna aur evaluate karna pucha ja raha hai: diye gaye type ke liye concept true hai ya false, aur kya syntax wahi keh raha hai jo woh keh raha hai.
Exercise 1.1 (L1)
Yeh diya gaya hai (with #include <concepts>)
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;Har ek ki value batao: Numeric<int>, Numeric<double>, Numeric<bool>, Numeric<char>, Numeric<std::string>, Numeric<float*>.
Recall Solution 1.1
Ek concept ek constexpr bool hai, isliye har ek literally true ya false hai.
Numeric<int>→ true (std::integral<int>true hai).Numeric<double>→ true (std::floating_point<double>true hai).Numeric<bool>→ true. Surprise! C++ meinboolek integral type hai, isliyestd::integral<bool>truehai.Numeric<char>→ true.charbhi integral hai.Numeric<std::string>→ false (na integral hai na floating).Numeric<float*>→ false. Float ka pointer floating-point type nahi hai; pointers integral bhi nahi hote.
true ki count: 4 (int, double, bool, char).
Exercise 1.2 (L1)
Neeche di gayi chaar constraint syntaxes mein se kaun si legal hain aur same cheez keh rahi hain ("T ko Numeric constraint karo")?
// (a) auto sq(Numeric auto x) { return x * x; }
// (b) template<Numeric T> T sq(T x) { return x * x; }
// (c) template<typename T> requires Numeric<T> T sq(T x) { return x * x; }
// (d) template<typename T> T sq(T x) requires Numeric<T> { return x * x; }Recall Solution 1.2
Charon (a, b, c, d) legal aur equivalent hain. Yeh parent note ki chaar syntaxes hain: abbreviated function template, constrained template parameter, requires-clause, trailing requires-clause. Legal forms ki sankhya: 4.
Level 2 — Application
Ab tum concepts apply karte ho: surviving overload choose karo, ya khud ek chhoti constraint likho.
Exercise 2.1 (L2)
Diya gaya hai (with #include <concepts>)
template<std::integral T> void f(T); // (A)
template<std::floating_point T> void f(T); // (B)Har call ke liye, chosen overload batao (ya "error"): f(5), f(3.14), f('a'), f(2.0f), f(true), f("hi").
Recall Solution 2.1
Jis candidate ki constraint false hai use silently remove kar diya jaata hai; call tabhi fail hoti hai jab zero bachte hain.
f(5)→intintegral hai → (A) bachta hai, (B) remove → (A).f(3.14)→doublefloating hai → (B).f('a')→charintegral hai → (A).f(2.0f)→floatfloating hai → (B).f(true)→boolintegral hai → (A).f("hi")→const char*na integral hai na floating → dono remove → error ("no matching function call").
Chosen: A, B, A, B, A, error. Successful calls ki sankhya: 5.
Exercise 2.2 (L2)
Ek concept Addable<T> likho jo tab exactly true ho jab do T values ke liye a + b compile ho aur result ka type T ho. Phir batao ki Addable<int> aur Addable<std::string> hold karte hain ya nahi.
Recall Solution 2.2
#include <concepts> // for std::same_as
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};requires(T a, T b) fictional test variables declare karta hai. { a + b } ek compound requirement hai: expression compile hona chahiye, aur -> std::same_as<T> uska type exactly T par pin karta hai.
Addable<int>→int + intinthai → true.Addable<std::string>→string + stringekstd::stringhai → true.
Dono hold karte hain: 2 of 2 true.
Level 3 — Analysis
Yahan tum reason karte ho ki kyun ek design aise behave karta hai — ambiguity, subsumption, silent removal.
Exercise 3.1 (L3)
Diya gaya hai (with #include <concepts> aur #include <type_traits>)
template<typename T> concept Integral = std::integral<T>;
template<typename T> concept SignedIntegral = Integral<T> && std::is_signed_v<T>;
template<Integral T> void g(T); // (A)
template<SignedIntegral T> void g(T); // (B)g(-7) (int) kaun sa overload call karta hai, aur kyun? g(7u) (unsigned) ke baare mein kya?
Recall Solution 3.1
g(-7) (B) call karta hai. int dono Integral aur SignedIntegral satisfy karta hai, isliye dono candidates survive karte hain. Jab dono candidates apply hote hain, subsumption tie break karta hai: SignedIntegral ko Integral<T> && ... ke roop mein define kiya gaya hai, isliye yeh strictly zyada maangta hai. Compiler zyada-constrained overload ko prefer karta hai → (B).
g(7u) (A) call karta hai. unsigned integral hai lekin signed nahi, isliye SignedIntegral<unsigned> false hai → (B) remove ho jaata hai. Sirf (A) bachta hai → (A). Koi ambiguity nahi kyunki sirf ek hi candidate bachta hai.
Exercise 3.2 (L3)
Explain karo ki concept Integral2<T> = std::integral<T> == true; aur Integral<T> = std::integral<T>; ek doosre ko subsume kyun nahi karte, bhaale woh logically identical hain. (Conceptual — subsumption ke liye kaunsi property hold karni chahiye?)
Recall Solution 3.2
Subsumption constraints ko unke atomic constraint structure se compare karta hai, logical truth se nahi. Do constraints tab subsume karte hain jab ek doosre ke syntactically same atomic pieces se built ho (&& / || "atoms" mein tod ke). std::integral<T> ek atom hai; std::integral<T> == true ek different atom hai (ek comparison expression). Compiler unhe unrelated atoms maanta hai aur prove nahi kar sakta ki ek doosre ko imply karta hai — isliye na to doosra subsume karta hai, aur agar dono apply hote hain toh tumhe ambiguity error milta hai. Lesson: refinements usi named concept ko && ke saath reuse karke express karo, condition dobara likhkar nahi.
Level 4 — Synthesis
Ab tum build karte ho multi-part concepts aur constrained templates jo ek stated problem solve karte hain.
Exercise 4.1 (L4)
Ek concept Printable<R> design karo jo tab true ho jab R ko range-for se iterate kiya ja sake aur har element ko std::cout << se bheja ja sake. Phir print_all likho jo isse constrain ho.
Recall Solution 4.1
#include <iostream> // std::cout, operator<<
#include <iterator> // std::begin, std::end
template<typename R>
concept Printable = requires(R r) {
std::begin(r); // (1) simple requirement: iterable
std::end(r);
requires requires(decltype(*std::begin(r)) x) { // (2) neeche terminology note dekho
std::cout << x;
};
};
template<Printable R>
void print_all(const R& r) {
for (const auto& x : r) std::cout << x << ' ';
}Terminology — dono requires ko seedha rakho (yahi confusing part hai):
requires(R r) { ... }— yeh outermost wala ek requires-expression hai. Yeh requirements ki ek{ ... }list kholti hai aur, ek whole ke roop mein, ekboolevaluate hoti hai.concept Printable = <woh bool>.- Lines
std::begin(r);aurstd::end(r);simple requirements hain — inhe bas compile hona chahiye. - Line
requires requires(...) { ... };mein back-to-back dono tarah kerequireshain:- Pehla word
requiresek nested requirement hai — ek requirement jiska kaam hai "yeh agle wali constraint hold karni chahiye". Ek nested requirement hamesha keywordrequiresse shuru hoti hai. - Doosra
requires(decltype(*std::begin(r)) x) { std::cout << x; }ek nayi, inner requires-expression hai. Yeh ek fictional elementxintroduce karta hai (jiska type element typedecltype(*std::begin(r))hai) aur demand karta hai kistd::cout << xcompile ho.
- Pehla word
Isliye pattern requires requires(...) = (nested requirement keyword) + (ek bilkul naya requires-expression). Humein yahan nested form chahiye kyunki hum element type ke baare mein ek constraint test karna chahte hain, directly R ke baare mein nahi.
- Result:
std::vector<int>Printablesatisfy karta hai;std::vector<NoStreamType>call site par reject hota hai, loop ke andar deep nahi.
Exercise 4.2 (L4)
Concepts use karke to_number ke do overloads likho: ek std::integral types ke liye jo value unchanged return kare, ek std::floating_point types ke liye jo value ko nearest integer par round kare aur long long mein cast kare. to_number(42), to_number(3.7), aur to_number(-2.4) ke results predict karo.
Recall Solution 4.2
std::llround kyun? Spec keh raha hai "nearest integer par round karo, long long ke roop mein". Standard library function jo exactly yahi ek call mein karta hai woh hai std::llround (#include <cmath> se): yeh ek floating value leta hai aur ek long long return karta hai, half away from zero round karta hai. Isliye hum ise use karte hain rather than, say, plain (long long) cast — ek bare cast zero ki taraf truncate karta hai aur "round to nearest" satisfy nahi karta (e.g. (long long)3.7 == 3, galat). Hum deliberately llround isisliye use karte hain kyunki yeh "nearest, as long long" requirement se match karta hai.
#include <concepts> // std::integral, std::floating_point
#include <cmath> // std::llround
template<std::integral T>
long long to_number(T x) { return x; } // (A)
template<std::floating_point T>
long long to_number(T x) { return std::llround(x); } // (B) returns long long directlyto_number(42)→int→ (A) → 42.to_number(3.7)→double→ (B) →llround(3.7)= 4.to_number(-2.4)→double→ (B) →llround(-2.4)= -2 (nearest integer; -2.4 rounds toward -2).
Jawab: 42, 4, -2.
Level 5 — Mastery
Subtle constraints ke under synthesis: subsumption ordering, degenerate/edge types, aur kyun ek tool choose karna.
Exercise 5.1 (L5)
Tumhare paas teen overloads hain (with #include <concepts>):
template<typename T> void h(T); // (A) unconstrained
template<std::integral T> void h(T); // (B)
template<std::integral T> requires (sizeof(T) >= 4) void h(T); // (C)Har call ke liye, chosen overload batao: h(int{}), h(char{}) (assume sizeof(char)==1), h(3.14).
Recall Solution 5.1
Ek constrained template ko ek less-constrained/unconstrained wale par prefer kiya jaata hai jab dono viable hon. Constrained ones mein, subsumption zyada-constrained ko choose karta hai.
h(int{})→sizeof(int)==4. Viable: (A) hamesha, (B) integral true, (C) integral &&4>=4true. (C) subsumes (B) subsumes (A) → (C).h(char{})→ integral true lekinsizeof(char)==1 < 4→ (C) remove. Viable: (A), (B). (B) constrained hai, (A) nahi → (B).h(3.14)→doubleintegral nahi → (B), (C) remove. Sirf (A) bachta hai → (A).
Chosen: (C), (B), (A).
Subsumption par note: (C) ki clause requires (sizeof(T) >= 4) integral ke upar ek extra atom hai; kyunki (B) aur (C) std::integral<T> atom share karte hain, (C) strictly zyada-constrained pehchana jaata hai. Refine karne ka yeh sahi tarika hai (Exercise 3.2 se contrast karo).
Exercise 5.2 (L5)
Is concept ko consider karo (with #include <concepts> for std::convertible_to, aur #include <cstddef> for std::size_t):
template<typename T>
concept Container = requires(T t) {
typename T::value_type; // (i) type requirement
{ t.size() } -> std::convertible_to<std::size_t>; // (ii) compound
t.begin(); // (iii) simple
};std::vector<int>, int, aur std::array<double,0> (zero-length array) ke liye batao ki Container hold karta hai ya nahi, aur degenerate case ko justify karo.
Recall Solution 5.2
Pehle, std::convertible_to<X, Y> <concepts> ka ek aur standard concept hai: yeh tab true hota hai jab ek X ko implicitly Y mein convert kiya ja sake. Yahan { t.size() } -> std::convertible_to<std::size_t> ka matlab hai "t.size() compile hota hai aur uska result std::size_t mein convert hota hai".
std::vector<int>→value_typehai,.size()std::size_treturn karta hai, aur.begin()hai → true.int→ koi nestedvalue_typenahi, koi.size()nahi, koi.begin()nahi → type requirement (i) pehle fail → false.std::array<double,0>→ yeh degenerate case hai: ek zero-length container.Containersirf poochta hai ki operations exist karti hain, container non-empty hai ya nahi nahi.std::array<double,0>mein phir bhivalue_type = doublehai, phir bhi ek valid.size()hai (0return karta hai), aur phir bhi.begin()hai (.end()ke barabar). Isliye concept hold karta hai → true.
Edge lesson: concepts type ki interface ki shape test karte hain, kabhi runtime contents nahi. Ek empty range ek valid range hai; concept correctly true kehta hai.
Recall Self-test summary (sirf finish karne ke baad kholo)
L1 Numeric ke liye true-count ::: 4 (int, double, bool, char)
L2 f("hi") outcome ::: error — dono overloads remove ho gaye
L3 g(-7) choose karta hai ::: (B) SignedIntegral, subsumption se
L4 to_number(3.7) ::: 4
L4 to_number(-2.4) ::: -2
L5 h(char{}) choose karta hai ::: (B), kyunki (C) ki size constraint false hai
L5 Container<std::array<double,0>> ::: true — interface exist karti hai empty container ke liye bhi