15-bo‘lim
Shablonlar va konseptlar
Bir marta yozib har qanday tur bilan ishlatish - shablon funksiyalar, shablon sinflar va C++20 konseptlari.
Ushbu bo‘lim mundarijasi
Shablon - bu kod emas, kod yozadigan retsept. Kompilyator har bir tur uchun alohida nusxa yaratadi - va shuning uchun shablonli kod virtual chaqiruvsiz, to'liq tezlikda ishlaydi.
Takrorlanish muammosi #
#include <print>
#include <string>
#include <vector>
#include <concepts>
#include <ranges>
#include <numeric>
#include <type_traits>
#include <algorithm>
// Bir xil mantiq, uch marta yozilgan
int kattaroqInt(int a, int b) { return a > b ? a : b; }
double kattaroqDouble(double a, double b) { return a > b ? a : b; }
std::string kattaroqSatr(const std::string& a, const std::string& b)
{
return a > b ? a : b;
}
int main()
{
std::println("int: {}", kattaroqInt(7, 12));
std::println("double: {}", kattaroqDouble(2.5, 1.75));
std::println("satr: {}", kattaroqSatr("olma", "behi"));
std::println("");
std::println("Uch funksiya - bir xil tana.");
std::println("Yangi tur qo'shsak - to'rtinchisini yozamiz.");
return 0;
}
int: 12
double: 2.5
satr: olma
Uch funksiya - bir xil tana.
Yangi tur qo'shsak - to'rtinchisini yozamiz.
Shablon funksiya #
template <typename T>
T kattarogi(const T& a, const T& b)
{
return a > b ? a : b;
}
// Ikki xil tur bilan ham ishlashi mumkin
template <typename A, typename B>
auto qoshuv(const A& a, const B& b)
{
return a + b;
}
int main()
{
std::println("int: {}", kattarogi(7, 12));
std::println("double: {}", kattarogi(2.5, 1.75));
std::println("satr: {}", kattarogi<std::string>("olma", "behi"));
std::println("char: {}", kattarogi('a', 'z'));
std::println("");
std::println("aralash qo'shuv:");
std::println(" int + double = {}", qoshuv(3, 0.5));
std::println(" int + int = {}", qoshuv(3, 4));
auto s = qoshuv(std::string{"salom "}, "dunyo");
std::println(" satr + literal = {}", s);
std::println("");
std::println("Bitta funksiya - cheksiz tur.");
return 0;
}
int: 12
double: 2.5
satr: olma
char: z
aralash qo'shuv:
int + double = 3.5
int + int = 7
satr + literal = salom dunyo
Bitta funksiya - cheksiz tur.
kattarogi(7, 12) da T ni yozmadik - kompilyator uni
argumentlardan chiqarib oldi.
Ba'zan yordam kerak:
kattarogi("olma", "behi") // T = const char* - MANZIL solishtiriladi
kattarogi<std::string>("olma", "behi") // T = std::string - MATN solishtiriladi
Birinchi holatda const char* ko'rsatkichlari
solishtiriladi - natija tasodifiy. Ikkinchisida
std::string yaratiladi va matn solishtiriladi.
Bu keng tarqalgan tuzoq. std::string_view yoki
oshkora <std::string> ishlating.
auto qaytish turi (qoshuv da) a + b ning
haqiqiy turini oladi - int + double uchun double.
Muammo: xato xabari #
template <typename T>
T ortacha(const std::vector<T>& v)
{
T jami{};
for (const auto& e : v) jami += e;
return jami / static_cast<T>(v.size());
}
int main()
{
std::vector<int> sonlar{10, 20, 30, 45};
std::vector<double> kasrlar{1.5, 2.5, 3.0};
std::println("int o'rtacha: {}", ortacha(sonlar));
std::println("double o'rtacha: {:.4f}", ortacha(kasrlar));
std::println("");
std::println("Endi satrlar bilan urinib ko'ring:");
std::println(" std::vector<std::string> s{{\"a\", \"b\"}};");
std::println(" ortacha(s);");
std::println("");
std::println("Kompilyator SHABLON ICHIDA xato beradi -");
std::println("operator/ topilmadi, deb. Lekin xato SIZNING");
std::println("kodingizda emas, ortacha() ning ichida ko'rsatiladi.");
return 0;
}
int o'rtacha: 26
double o'rtacha: 2.3333
Endi satrlar bilan urinib ko'ring:
std::vector<std::string> s{"a", "b"};
ortacha(s);
Kompilyator SHABLON ICHIDA xato beradi -
operator/ topilmadi, deb. Lekin xato SIZNING
kodingizda emas, ortacha() ning ichida ko'rsatiladi.
Konseptlarsiz shablon xatosi shablonning ichida paydo bo'ladi:
error: no match for 'operator/' (operand types are
'std::string' and 'std::string')
return jami / static_cast<T>(v.size());
~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~
note: in instantiation of 'T ortacha(...) [with T = std::string]'
Bu C++ ning eng yomon nom qozongan xususiyati edi: sodda xato o'nlab qator xabar keltirardi, ba'zan yuzlab.
Muammo shundaki, shablon talabini bildirmaydi.
"T bo'lish operatorini qo'llab-quvvatlashi kerak" -
bu faqat tanadan bilinadi.
C++20 konseptlari aynan shuni hal qiladi: talabni imzoda yozamiz va xato chaqiruv joyida paydo bo'ladi.
Konsept #
template <typename T>
concept Sonli = std::integral<T> || std::floating_point<T>;
template <Sonli T>
double ortacha(const std::vector<T>& v)
{
if (v.empty()) return 0.0;
double jami = 0;
for (const auto& e : v) jami += static_cast<double>(e);
return jami / static_cast<double>(v.size());
}
int main()
{
std::vector<int> sonlar{10, 20, 30, 45};
std::vector<double> kasrlar{1.5, 2.5, 3.0};
std::println("int: {:.4f}", ortacha(sonlar));
std::println("double: {:.4f}", ortacha(kasrlar));
std::println("");
std::println("Konsept tekshiruvlari:");
std::println(" Sonli<int> {}", Sonli<int>);
std::println(" Sonli<double> {}", Sonli<double>);
std::println(" Sonli<char> {}", Sonli<char>);
std::println(" Sonli<bool> {}", Sonli<bool>);
std::println(" Sonli<std::string> {}", Sonli<std::string>);
std::println("");
std::println("std::string bilan chaqirsak, xato SHU YERDA -");
std::println("shablon ichida emas: 'constraint not satisfied'.");
return 0;
}
int: 26.2500
double: 2.3333
Konsept tekshiruvlari:
Sonli<int> true
Sonli<double> true
Sonli<char> true
Sonli<bool> true
Sonli<std::string> false
std::string bilan chaqirsak, xato SHU YERDA -
shablon ichida emas: 'constraint not satisfied'.
Diqqat qiling: birinchi ortacha int vektor uchun
26 qaytardi, ikkinchisi esa 26.2500.
Sabab birinchi versiyaning imzosida:
template <typename T>
T ortacha(const std::vector<T>& v) // T = int -> BUTUN bo'lish
T int bo'lganda jami / v.size() butun sonli
bo'lish qiladi va 105 / 4 26 beradi - kasr qismi
yo'qoladi.
Ikkinchi versiya double qaytaradi va hisobni ham
double da bajaradi.
Bu shablonlarning nozik tomoni: qaytish turini
parametrga bog'lash ko'pincha noto'g'ri javob beradi.
O'rtacha qiymat tabiatan kasr - shuning uchun uni
double qilish to'g'riroq, kirish turi qanday
bo'lishidan qat'i nazar.
Bir xil ma'noni to'rt xil yozish mumkin:
// 1. Shablon parametri o'rnida
template <Sonli T>
double f(const std::vector<T>& v);
// 2. requires bandi
template <typename T>
requires Sonli<T>
double f(const std::vector<T>& v);
// 3. Orqadagi requires
template <typename T>
double f(const std::vector<T>& v) requires Sonli<T>;
// 4. Qisqartirilgan shablon (eng qisqa)
double f(const std::vector<Sonli auto>& v);
Birinchi va to'rtinchi eng ko'p ishlatiladi. Ikkinchisi murakkab shartlar uchun qulay:
template <typename T>
requires Sonli<T> && (sizeof(T) >= 4)
Standart kutubxonada tayyor konseptlar ko'p:
std::integral, std::floating_point,
std::copyable, std::invocable, std::same_as,
std::derived_from, std::ranges::range.
requires ifodasi #
// "T da bu amallar bo'lishi kerak" deb yozamiz
template <typename T>
concept Chopetiladigan = requires(const T& t) {
{ t.matn() } -> std::convertible_to<std::string>;
{ t.raqam() } -> std::same_as<int>;
};
struct Kitob
{
std::string nom;
int sahifa{};
std::string matn() const { return nom; }
int raqam() const { return sahifa; }
};
struct Rasm
{
std::string fayl;
std::string matn() const { return fayl; }
// raqam() YO'Q
};
template <Chopetiladigan T>
void korsat(const T& t)
{
std::println(" {} ({} sahifa)", t.matn(), t.raqam());
}
int main()
{
std::println("Konsept tekshiruvi:");
std::println(" Chopetiladigan<Kitob> {}", Chopetiladigan<Kitob>);
std::println(" Chopetiladigan<Rasm> {}", Chopetiladigan<Rasm>);
std::println(" Chopetiladigan<int> {}", Chopetiladigan<int>);
std::println("");
std::println("kitoblar:");
korsat(Kitob{"O'tkan kunlar", 368});
korsat(Kitob{"Mehrobdan chayon", 412});
// korsat(Rasm{"a.png"}); ← kompilyatsiya xatosi, aniq sababi bilan
return 0;
}
Konsept tekshiruvi:
Chopetiladigan<Kitob> true
Chopetiladigan<Rasm> false
Chopetiladigan<int> false
kitoblar:
O'tkan kunlar (368 sahifa)
Mehrobdan chayon (412 sahifa)
requires ifodasi ichida nima yoziladirequires(const T& t) { ... } bloki ichida to'rt xil
talab bo'lishi mumkin:
| Yozuv | Ma'nosi |
|---|---|
t.f(); | t.f() yozish mumkin |
{ t.f() } -> std::same_as<int>; | Qaytish turi aynan int |
typename T::qiymat_turi; | Bunday ichki tur bor |
requires Boshqa<T>; | Boshqa konseptni ham qanoatlantiradi |
Muhim: bu ifodalar hech qachon bajarilmaydi. Ular faqat "kompilyatsiya qilinadimi" degan savolga javob beradi.
Shuning uchun t obyektining qiymati muhim emas -
uning turi muhim.
Konsept bool qiymat ham beradi:
static_assert(Chopetiladigan<Kitob>);
if constexpr (Chopetiladigan<T>) { ... }
Shablon sinf #
template <typename T, std::size_t N>
class Massiv
{
T ma_[N]{};
public:
constexpr std::size_t hajm() const { return N; }
T& operator[](std::size_t i) { return ma_[i]; }
const T& operator[](std::size_t i) const { return ma_[i]; }
T* begin() { return ma_; }
T* end() { return ma_ + N; }
const T* begin() const { return ma_; }
const T* end() const { return ma_ + N; }
void toldir(const T& q)
{
for (auto& e : ma_) e = q;
}
};
int main()
{
Massiv<int, 5> sonlar;
for (std::size_t i = 0; i < sonlar.hajm(); ++i) {
sonlar[i] = static_cast<int>((i + 1) * (i + 1));
}
std::print("sonlar ({} ta):", sonlar.hajm());
for (int x : sonlar) std::print(" {}", x);
std::println("");
Massiv<std::string, 3> satrlar;
satrlar.toldir("bo'sh");
satrlar[1] = "to'ldirildi";
std::print("satrlar:");
for (const auto& s : satrlar) std::print(" [{}]", s);
std::println("");
std::println("");
std::println("hajm kompilyatsiya paytida ma'lum: {}", sonlar.hajm());
std::println("sizeof(Massiv<int,5>) = {} bayt", sizeof(sonlar));
std::println("uyum ajratish YO'Q - hammasi stekda.");
std::println("");
std::println("yig'indi: {}", std::accumulate(sonlar.begin(), sonlar.end(), 0));
return 0;
}
sonlar (5 ta): 1 4 9 16 25
satrlar: [bo'sh] [to'ldirildi] [bo'sh]
hajm kompilyatsiya paytida ma'lum: 5
sizeof(Massiv<int,5>) = 20 bayt
uyum ajratish YO'Q - hammasi stekda.
yig'indi: 55
template <typename T, std::size_t N> da N - qiymat
parametri. U kompilyatsiya paytida ma'lum bo'lishi
kerak.
Qiymat parametrlari bo'lishi mumkin:
| Tur | Misol |
|---|---|
| Butun son | template <int N> |
bool | template <bool Tekshir> |
| Sanoq (enum) | template <Rejim R> |
| Ko'rsatkich/havola | template <auto& Obyekt> |
| Har qanday (C++17) | template <auto Q> |
std::array<T, N> aynan shu naqshda qurilgan.
Bu nol xarajatli mavhumlik: Massiv<int, 5>
mashina kodida oddiy int[5] ga aylanadi. Hech qanday
ko'rsatkich, hisoblagich yoki uyum ajratish yo'q.
if constexpr - kompilyatsiya paytida tarmoqlanish #
template <typename T>
std::string tavsif(const T& q)
{
if constexpr (std::is_same_v<T, bool>) {
return q ? "mantiqiy: rost" : "mantiqiy: yolg'on";
}
else if constexpr (std::integral<T>) {
return std::format("butun son: {} ({} bayt)", q, sizeof(T));
}
else if constexpr (std::floating_point<T>) {
return std::format("kasr son: {:.4f}", q);
}
else if constexpr (requires { std::string{q}; }) {
return std::format("satr: '{}'", std::string{q});
}
else {
return "noma'lum tur";
}
}
int main()
{
std::println("{}", tavsif(true));
std::println("{}", tavsif(42));
std::println("{}", tavsif(42LL));
std::println("{}", tavsif(3.14159));
std::println("{}", tavsif(2.5f));
std::println("{}", tavsif(std::string{"salom"}));
std::println("{}", tavsif("literal"));
std::println("");
std::println("Har bir chaqiruvda FAQAT bitta tarmoq");
std::println("kompilyatsiya qilinadi - qolganlari umuman");
std::println("mashina kodiga tushmaydi.");
return 0;
}
mantiqiy: rost
butun son: 42 (4 bayt)
butun son: 42 (8 bayt)
kasr son: 3.1416
kasr son: 2.5000
satr: 'salom'
satr: 'literal'
Har bir chaqiruvda FAQAT bitta tarmoq
kompilyatsiya qilinadi - qolganlari umuman
mashina kodiga tushmaydi.
if constexpr va oddiy if farqiOddiy if da ikkala tarmoq kompilyatsiya
qilinishi kerak:
if (std::is_same_v<T, std::string>) {
return q.substr(0, 3); // T = int bo'lsa XATO
}
T int bo'lganda ham q.substr(...) kompilyatsiya
qilinadi - va yiqiladi.
if constexpr da esa shart yolg'on bo'lgan tarmoq
tekshirilmaydi ham:
if constexpr (std::is_same_v<T, std::string>) {
return q.substr(0, 3); // T = int bo'lsa - e'tiborsiz
}
Bu shablonlarda ajralmas vosita. Undan oldin bu ish
uchun murakkab enable_if texnikasi ishlatilardi.
Diqqat: if constexpr faqat shablon ichida
foydali - oddiy funksiyada shart allaqachon ma'lum.
Amaliy misol: universal kesh #
template <typename Kalit, typename Qiymat>
requires std::equality_comparable<Kalit>
class Kesh
{
struct Yozuv { Kalit kalit; Qiymat qiymat; };
std::vector<Yozuv> yozuvlar_;
std::size_t chegara_;
int tegdi_{};
int tegmadi_{};
public:
explicit Kesh(std::size_t chegara) : chegara_(chegara) {}
const Qiymat* top(const Kalit& k)
{
auto it = std::ranges::find(yozuvlar_, k, &Yozuv::kalit);
if (it == yozuvlar_.end()) { ++tegmadi_; return nullptr; }
++tegdi_;
return &it->qiymat;
}
void qoy(Kalit k, Qiymat q)
{
if (yozuvlar_.size() >= chegara_) yozuvlar_.erase(yozuvlar_.begin());
yozuvlar_.push_back({std::move(k), std::move(q)});
}
int tegdi() const { return tegdi_; }
int tegmadi() const { return tegmadi_; }
std::size_t soni() const { return yozuvlar_.size(); }
};
int main()
{
Kesh<std::string, int> yosh{3};
yosh.qoy("Husanboy", 25);
yosh.qoy("Malika", 22);
yosh.qoy("Nodira", 24);
for (const auto* nom : {"Husanboy", "Aziza", "Nodira", "Aziza"}) {
const int* q = yosh.top(nom);
std::println(" {:<10} {}", nom, q ? std::format("{}", *q) : "topilmadi");
}
std::println("");
std::println("tegdi: {}, tegmadi: {}", yosh.tegdi(), yosh.tegmadi());
std::println("");
std::println("chegaradan oshirish:");
yosh.qoy("Kamola", 23);
std::println(" yozuvlar soni: {} (chegara 3)", yosh.soni());
std::println(" Husanboy hali bormi: {}", yosh.top("Husanboy") != nullptr);
std::println(" Kamola bormi: {}", yosh.top("Kamola") != nullptr);
std::println("");
Kesh<int, std::string> nomlar{2};
nomlar.qoy(1, "birinchi");
nomlar.qoy(2, "ikkinchi");
const std::string* s = nomlar.top(2);
std::println("boshqa turlar bilan ham: {}", s ? *s : "yo'q");
return 0;
}
Husanboy 25
Aziza topilmadi
Nodira 24
Aziza topilmadi
tegdi: 2, tegmadi: 2
chegaradan oshirish:
yozuvlar soni: 3 (chegara 3)
Husanboy hali bormi: false
Kamola bormi: true
boshqa turlar bilan ham: ikkinchi
- Bir xil mantiqni uch tur uchun uch marta yozing.
- Uni bitta shablon funksiyaga aylantiring.
kattarogi("a", "b")vakattarogi<std::string>("a", "b")ni solishtiring.Sonlikonseptini yozing vastd::stringbilan chaqiring.- Konseptni
static_assertbilan tekshiring. requiresifodasi bilan "shu a'zo funksiya bor" talabini yozing.template <typename T, std::size_t N>bilan shablon sinf yozing.sizeofbilan uning uyum ajratmasligini tasdiqlang.if constexprbilan tur bo'yicha tarmoqlaning.- Oddiy
ifga almashtiring - nima bo'ladi?
Xulosa #
- Shablon - kod emas, kompilyator uchun retsept; har bir tur uchun alohida kod yaratiladi.
- Shuning uchun shablonli kod virtual chaqiruvsiz, to'liq tezlikda ishlaydi.
- Kompilyator shablon argumentini argumentlardan chiqarib oladi.
kattarogi("a", "b")daT = const char*- manzillar solishtiriladi.- Konseptsiz shablon talabini bildirmaydi - xato uning ichida paydo bo'ladi.
- Konsept talabni imzoga ko'chiradi va xatoni chaqiruv joyiga olib chiqadi.
requires(const T& t) { ... }ichidagi ifodalar hech qachon bajarilmaydi.- Shablon parametri tur bo'lishi shart emas -
template <typename T, std::size_t N>. - Bu nol xarajatli:
Massiv<int, 5>mashina kodidaint[5]ga aylanadi. if constexprda yolg'on tarmoq umuman kompilyatsiya qilinmaydi.- Narxi: kompilyatsiya vaqti va ikkilik fayl hajmi.
Keyingi bo'limda xatolarni boshqarish - istisnolar, optional va expected ni ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.