رفتن به محتوا

پیش‌نمایش ‎C++‎23 و ‎C++‎26

std::expected، std::print، استنتاج this، بهبودهای محدوده‌ها در ‎C++‎23، و پیش‌نمایش ویژگی‌های آینده ‎C++‎26 مانند قراردادها (Contracts)، بازتاب (Reflection)، و تطبیق الگو (Pattern Matching) را کاوش کنید.

تکامل ادامه دارد

‎C++‎23 یک نسخه «تکمیلی» است که شکاف‌های باقیمانده از ‎C++‎20 را پر می‌کند و ویژگی‌های بسیار درخواست‌شده را اضافه می‌کند. std::expected مدیریت خطا به سبک Rust را می‌آورد، std::print خروجی مدرن فراهم می‌کند، استنتاج this الگوهای قدرتمند متاپروگرمینگ را ممکن می‌سازد، و محدوده‌ها ده‌ها نمای جدید دریافت می‌کنند. در همین حال، ‎C++‎26 در حال شکل‌گیری به‌عنوان یک نسخه بزرگ دیگر است با قراردادها (Contracts)، بازتاب (Reflection)، و تطبیق الگو (Pattern Matching) — ویژگی‌هایی که نحوه نوشتن کد ‎C++‎ ایمن و بیانگر را اساساً تغییر خواهند داد.

std::expected: مدیریت خطای مدرن

std::expected یا یک مقدار موفق از نوع T یا یک خطا از نوع E نگه می‌دارد. در ترکیب با عملیات مونادیک (.and_then()، .transform()، .or_else())، مدیریت خطای ترکیب‌پذیر بدون استثناها را ممکن می‌سازد:

expected.cpp
#include <iostream>
#include <expected>
#include <string>
#include <charconv>
#include <system_error>

enum class ParseError {
    empty_input,
    invalid_format,
    out_of_range
};

std::expected<int, ParseError> parse_int(std::string_view sv) {
    if (sv.empty()) return std::unexpected(ParseError::empty_input);

    int result{};
    auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), result);

    if (ec == std::errc::invalid_argument)
        return std::unexpected(ParseError::invalid_format);
    if (ec == std::errc::result_out_of_range)
        return std::unexpected(ParseError::out_of_range);
    if (ptr != sv.data() + sv.size())
        return std::unexpected(ParseError::invalid_format);

    return result;
}

std::expected<int, ParseError> double_if_positive(int val) {
    if (val <= 0) return std::unexpected(ParseError::out_of_range);
    return val * 2;
}

int main() {
    // Direct usage
    auto result = parse_int("42");
    if (result) {
        std::cout << "Parsed: " << *result << "\n";
    } else {
        std::cout << "Error code: " << static_cast<int>(result.error()) << "\n";
    }

    // Monadic chaining — like Rust's Result::and_then
    auto chained = parse_int("21")
        .and_then(double_if_positive)       // int -> expected<int, E>
        .transform([](int v) { return v + 1; })  // int -> int (wrapped)
        .or_else([](ParseError e) -> std::expected<int, ParseError> {
            std::cout << "Recovering from error\n";
            return 0;  // default value on error
        });

    std::cout << "Chained result: " << *chained << "\n";  // 43

    return 0;
}

std::print و std::println قالب‌بندی std::format را با خروجی ترکیب می‌کنند و جایگزین std::cout << برای اکثر موارد استفاده می‌شوند. سریع‌تر، ایمن از نظر نوع و تولیدکننده کد خوانا هستند:

print.cpp
#include <print>
#include <vector>
#include <string>
#include <ranges>

int main() {
    // Basic printing — no more << chains!
    std::println("Hello, {}!", "C++23");
    std::print("No newline here. ");
    std::println("But here.");

    // Formatting works just like std::format
    int x = 42;
    double pi = 3.14159;
    std::println("x = {}, pi = {:.2f}", x, pi);
    std::println("Hex: {:#x}, Binary: {:#b}", 255, 255);

    // Print to stderr
    std::println(stderr, "Warning: something happened");

    // Containers and ranges (C++23 formatter support)
    std::vector<int> v{1, 2, 3, 4, 5};
    std::println("Vector: {}", v);  // [1, 2, 3, 4, 5]

    // Formatted table
    std::println("{:<15} {:>10} {:>10}", "Name", "Score", "Grade");
    std::println("{:<15} {:>10} {:>10}", "Alice", 95, "A");
    std::println("{:<15} {:>10} {:>10}", "Bob", 87, "B+");

    return 0;
}

بهبودهای محدوده‌ها: zip، chunk، slide

‎C++‎23 نماهای محدوده جدید و قدرتمندی اضافه می‌کند که الگوهای رایجی را پوشش می‌دهند که قبلاً نیاز به حلقه‌های دستی یا کتابخانه‌های شخص ثالث داشتند:

ranges_cpp23.cpp
#include <print>
#include <ranges>
#include <vector>
#include <string>

int main() {
    std::vector<std::string> names{"Alice", "Bob", "Carol"};
    std::vector<int> scores{95, 87, 92};

    // zip — combine multiple ranges element-wise
    for (auto [name, score] : std::views::zip(names, scores)) {
        std::println("{}: {}", name, score);
    }

    std::vector<int> data{1, 2, 3, 4, 5, 6, 7, 8, 9};

    // chunk — split into groups of N
    for (auto chunk : data | std::views::chunk(3)) {
        std::print("[ ");
        for (int v : chunk) std::print("{} ", v);
        std::println("]");
    }
    // [ 1 2 3 ] [ 4 5 6 ] [ 7 8 9 ]

    // slide — sliding window of size N
    for (auto window : data | std::views::slide(3)) {
        std::print("( ");
        for (int v : window) std::print("{} ", v);
        std::println(")");
    }
    // ( 1 2 3 ) ( 2 3 4 ) ( 3 4 5 ) ... ( 7 8 9 )

    // cartesian_product — all combinations
    std::vector<char> suits{'H', 'D', 'C', 'S'};
    std::vector<int> ranks{1, 2, 3};
    for (auto [suit, rank] : std::views::cartesian_product(suits, ranks)) {
        std::print("{}{}  ", suit, rank);
    }
    std::println("");

    // enumerate — index + value (finally!)
    for (auto [i, name] : std::views::enumerate(names)) {
        std::println("[{}] {}", i, name);
    }

    return 0;
}

استنتاج this

ویژگی استنتاج this در ‎C++‎23 (همچنین «پارامتر صریح شیء» نامیده می‌شود) به توابع عضو اجازه می‌دهد this را به‌عنوان یک پارامتر صریح و استنتاج‌شده بگیرند. این نیاز به تکرار سربارگذاری‌های const/غیرconst را حذف می‌کند، لامبداهای بازگشتی را ممکن می‌سازد، و CRTP را ساده‌تر می‌کند:

struct Widget {
    std::string name;

    // Before ‎C++‎23: two identical overloads
    // const std::string& get_name() const { return name; }
    // std::string& get_name() { return name; }

    // ‎C++‎23: one function handles both
    template<typename Self>
    auto&& get_name(this Self&& self) {
        return std::forward<Self>(self).name;
    }
};

پارامتر this Self&& self استنتاج می‌کند که آیا شیء const، غیرconst، lvalue یا rvalue است — و به‌درستی ارسال (forward) می‌کند. این الگو، به نام «اصطلاح استنتاج this»، می‌تواند مجموعه سربارگذاری‌های توابع عضو را نصف کند.

پیش‌نمایش ‎C++‎26: قراردادها، بازتاب، تطبیق الگو

انتظار می‌رود ‎C++‎26 شامل چندین ویژگی تحول‌آفرین باشد:

قراردادها (Contracts) — pre، post و contract_assert امکان تعیین پیش‌شرط‌ها، پس‌شرط‌ها و تأییدیه‌هایی را فراهم می‌کنند که سیستم ساخت می‌تواند فعال یا غیرفعال کند:

int sqrt_int(int x)
    pre(x >= 0)
    post(r: r * r <= x)
{
    // implementation
}

بازتاب ایستا (Static Reflection) — بررسی انواع، اعضا و شمارنده‌ها در زمان کامپایل. تولید کد بر اساس فیلدهای ساختار، استخراج خودکار سریال‌سازی، و موارد بیشتر — همه بدون ماکرو.

تطبیق الگو (Pattern Matching) — عبارات inspect امکان تطبیق بر روی انواع، مقادیر و ساختار را فراهم می‌کنند:

inspect (variant_value) {
    <int> i    => std::println("int: {}", i);
    <string> s => std::println("string: {}", s);
    __         => std::println("other");
};

فرستنده/گیرنده (std::execution) — یک چارچوب استاندارد برای اجرای ناهمگام که همزمانی ساختاریافته فراهم می‌کند و جایگزین پیاده‌سازی‌های موقتی استخر نخ می‌شود.

بهترین شیوه

برای به‌روز ماندن با تکامل ‎C++‎:

1. یک استاندارد حداقلی برای پروژه خود هدف قرار دهید (‎C++‎17 خط مبنای محکمی برای اکثر کدهای تولیدی امروزی است)
2. از فلگ‌های کامپایلر برای انتخاب استاندارد استفاده کنید: -std=c++17، -std=c++20، -std=c++23
3. پشتیبانی کامپایلر را بررسی کنید در cppreference.com/compiler_support — همه کامپایلرها هر ویژگی را پیاده‌سازی نمی‌کنند
4. از ماکروهای تست ویژگی مانند __cpp_concepts، __cpp_lib_expected برای استفاده شرطی از ویژگی‌های جدیدتر استفاده کنید
5. پیشنهادات را بخوانید (wg21.link/pXXXX) برای ویژگی‌هایی که قصد اتخاذ آن‌ها را دارید
6. هر ویژگی جدید را دنبال نکنید — آنچه کدتان را ساده‌تر و صحیح‌تر می‌کند اتخاذ کنید، نه فقط جدیدتر

دام

اتخاذ زودهنگام آخرین ویژگی‌های استاندارد هزینه‌های واقعی دارد:

باگ‌های کامپایلر: ویژگی‌های جدید اغلب باگ‌های پیاده‌سازی دارند که فقط از طریق استفاده تولیدی کشف می‌شوند. ماژول‌ها و کوروتین‌های ‎C++‎20 برای مثال، ۲ تا ۳ سال پس از استانداردسازی باگ‌های مهم کامپایلری داشتند.

ابزارهای ناقص: دیباگرها، پروفایلرها و تحلیل‌گرهای ایستا ممکن است به‌طور کامل از جدیدترین ویژگی‌ها پشتیبانی نکنند. پشتیبانی IDE (IntelliSense، تکمیل کد) اغلب عقب می‌ماند.

قابلیت حمل: اگر کد شما باید روی چندین پلتفرم کامپایل شود (لینوکس، macOS، ویندوز، تعبیه‌شده)، به اشتراک پشتیبانی تمام کامپایلرهای هدف محدود هستید.

آمادگی تیم: ویژگی‌های جدید نیاز به یادگیری در سطح تیم دارند. ویژگی‌ای که فقط یک توسعه‌دهنده می‌فهمد، گلوگاه نگهداری ایجاد می‌کند.

نکات کلیدی
  • std::expected مدیریت خطا به سبک Rust با زنجیره‌سازی مونادیک (.and_then()، .transform()) را ممکن می‌سازد
  • std::print / std::println جایگزین std::cout << با خروجی قالب‌بندی‌شده تمیز، سریع و ایمن از نظر نوع می‌شوند
  • محدوده‌های ‎C++‎23 نماهای zip، chunk، slide، cartesian_product و enumerate را اضافه می‌کنند
  • استنتاج this تکرار سربارگذاری توابع عضو const/غیر-const را حذف می‌کند
  • ‎C++‎26 قراردادها، بازتاب ایستا، تطبیق الگو و std::execution برای ناهمگام را هدف قرار می‌دهد
  • استانداردهای جدید را به‌تدریج اتخاذ کنید — پشتیبانی کامپایلر را بررسی کنید، به‌دقت تست کنید، و آمادگی تیم را اطمینان حاصل کنید

آزمون — دانش خود را بسنجید

(15 امتیاز)

1. `std::expected<int, Error>` چه چیزی را نمایش می‌دهد؟

2. `std::views::zip(names, scores)` چه چیزی تولید می‌کند؟

3. کدام ویژگی ‎C++‎26 امکان تعیین پیش‌شرط‌ها و پس‌شرط‌های تابع را فراهم می‌کند؟