15-bo‘lim

Shablonlar va konseptlar

Bir marta yozib har qanday tur bilan ishlatish - shablon funksiyalar, shablon sinflar va C++20 konseptlari.

🕑 16 daqiqa o‘qish 📄 874 so‘z 👁 3 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Takrorlanish muammosi
  2. Shablon funksiya
  3. Muammo: xato xabari
  4. Konsept
  5. requires ifodasi
  6. Shablon sinf
  7. if constexpr - kompilyatsiya paytida tarmoqlanish
  8. Amaliy misol: universal kesh
  9. Xulosa

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 #

C++
#include <print>
#include <string>
#include <vector>
#include <concepts>
#include <ranges>
#include <numeric>
#include <type_traits>
#include <algorithm>
C++
// 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;
}
Natija
int:    12
double: 2.5
satr:   olma

Uch funksiya - bir xil tana.
Yangi tur qo'shsak - to'rtinchisini yozamiz.

Shablon funksiya #

C++
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;
}
Natija
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.
Shablon argumentini kompilyator o'zi topadi

kattarogi(7, 12) da T ni yozmadik - kompilyator uni argumentlardan chiqarib oldi.

Ba'zan yordam kerak:

KOD
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 #

C++
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;
}
Natija
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.
Shablon xatolari qayerda ko'rsatiladi

Konseptlarsiz shablon xatosi shablonning ichida paydo bo'ladi:

KOD
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 #

C++
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;
}
Natija
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'.
Ikki versiya bir xil ma'lumotdan boshqa javob berdi

Diqqat qiling: birinchi ortacha int vektor uchun 26 qaytardi, ikkinchisi esa 26.2500.

Sabab birinchi versiyaning imzosida:

KOD
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.

Konsept yozishning to'rt usuli

Bir xil ma'noni to'rt xil yozish mumkin:

KOD
// 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:

KOD
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.

Shablon - kod emas, kod yozadigan retsept MANBA KODI - bitta shablon template <typename T> T kattarogi(const T& a, const T& b); kompilyator instansiya yaratadi kattarogi<int> int kattarogi(const int&, const int&); alohida mashina kodi kattarogi<double> double kattarogi(const double&, const double&); alohida mashina kodi kattarogi<string> string kattarogi(const string&, const string&); alohida mashina kodi KONSEPTSIZ Talab faqat TANADA bilinadi. Xato shablon ichida ko'rsatiladi. O'nlab qator tushunarsiz xabar. KONSEPT BILAN Talab IMZODA yozilgan. Xato chaqiruv joyida ko'rsatiladi. "constraint not satisfied" - bir qator. Narxi: kompilyatsiya vaqti va ikkilik fayl hajmi Har bir tur uchun alohida kod yaratiladi - shuning uchun bajarilish tezligi maksimal. Lekin 50 tur uchun 50 nusxa - kompilyatsiya sekinlashadi, fayl kattalashadi.
Shablon har bir tur uchun alohida, to'liq optimallashtirilgan kod yaratadi

requires ifodasi #

C++
// "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;
}
Natija
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 yoziladi

requires(const T& t) { ... } bloki ichida to'rt xil talab bo'lishi mumkin:

YozuvMa'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:

KOD
static_assert(Chopetiladigan<Kitob>);
if constexpr (Chopetiladigan<T>) { ... }

Shablon sinf #

C++
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;
}
Natija
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
Shablon parametri tur bo'lishi shart emas

template <typename T, std::size_t N> da N - qiymat parametri. U kompilyatsiya paytida ma'lum bo'lishi kerak.

Qiymat parametrlari bo'lishi mumkin:

TurMisol
Butun sontemplate <int N>
booltemplate <bool Tekshir>
Sanoq (enum)template <Rejim R>
Ko'rsatkich/havolatemplate <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 #

C++
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;
}
Natija
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 farqi

Oddiy if da ikkala tarmoq kompilyatsiya qilinishi kerak:

KOD
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:

KOD
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 #

C++
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;
}
Natija
  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
Amaliy topshiriq
  1. Bir xil mantiqni uch tur uchun uch marta yozing.
  2. Uni bitta shablon funksiyaga aylantiring.
  3. kattarogi("a", "b") va kattarogi<std::string>("a", "b") ni solishtiring.
  4. Sonli konseptini yozing va std::string bilan chaqiring.
  5. Konseptni static_assert bilan tekshiring.
  6. requires ifodasi bilan "shu a'zo funksiya bor" talabini yozing.
  7. template <typename T, std::size_t N> bilan shablon sinf yozing.
  8. sizeof bilan uning uyum ajratmasligini tasdiqlang.
  9. if constexpr bilan tur bo'yicha tarmoqlaning.
  10. Oddiy if ga 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") da T = 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 kodida int[5] ga aylanadi.
  • if constexpr da 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.

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.