انتقل إلى المحتوى

معاينة C++23 وC++26

استكشف std::expected, std::print لـ C++23، واستنتاج ذلك، وتحسينات النطاقات، ومعاينة ميزات C++26 القادمة مثل العقود، والانعكاس، ومطابقة الأنماط.

التطور مستمر

C++23 هو إصدار "مكتمل" يملأ الفجوات التي خلفها C++20 ويضيف الميزات المطلوبة بشدة. std::expected يوفر معالجة الأخطاء بأسلوب Rust، ويوفر std::print مخرجات حديثة، استنتاج هذا يتيح أنماط البرمجة الفوقية القوية، وتكتسب النطاقات العشرات من طرق العرض الجديدة. وفي الوقت نفسه، يتشكل C++26 ليكون إصدارًا رئيسيًا آخر يحتوي على العقود والانعكاس ومطابقة الأنماط** — وهي ميزات ستغير بشكل أساسي طريقة كتابتنا لتعليمات C++ الآمنة والمعبرة.

std::expected: Modern Error Handling

يحمل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;
}

تحسينات النطاقات: الرمز البريدي، القطعة، الشريحة

يضيف 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;
}

نستنتج هذا

يتيح استنتاج هذا الخاص بـ C++23 (يُسمى أيضًا "معلمة الكائن الصريحة") لوظائف الأعضاء أخذthisكمعلمة صريحة ومستنتجة. يؤدي هذا إلى إلغاء الحاجة إلى تكرار الأحمال الزائدة لـconst/non-const، وتمكين عمليات lambda العودية، وتبسيط 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;
 }
};

The parameter this Self&& self deduces whether the object is const, non-const أو lvalue أو rvalue - وإعادة التوجيه بشكل صحيح. هذا النمط، الذي يُطلق عليه مصطلح "استنتاج هذا"،، يمكن أن يخفض مجموعات التحميل الزائد لوظائف الأعضاء إلى النصف.

معاينة C++26: العقود، الانعكاس، مطابقة الأنماط

ومن المتوقع أن يتضمن C++26 العديد من الميزات التحويلية:

العقود — تسمحpreوpostوcontract_assertبالشروط المسبقة والشروط اللاحقة والتأكيدات التي يمكن لنظام البناء تمكينها أو تعطيلها:

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

Static Reflection — Inspect types, members, and enumerators at compile time. Generate code based on struct fields, auto-derive serialization, and more — all without macros.

Pattern Matching — inspect expressions allow matching on types, values, and structure:

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

Sender/Receiver (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 وcoroutines تحتوي على أخطاء كبيرة في المترجم لمدة 2-3 سنوات بعد التوحيد القياسي.

أدوات غير مكتملة: قد لا تدعم أدوات تصحيح الأخطاء وملفات التعريف والمحللات الثابتة أحدث الميزات بشكل كامل. غالبًا ما يتأخر دعم IDE (التحسس الذكي، إكمال التعليمات البرمجية).

قابلية النقل: إذا كان يجب تجميع التعليمات البرمجية الخاصة بك على أنظمة أساسية متعددة (Linux وmacOS وWindows والمضمنة)، فأنت مقيد بالتقاطع بين ما تدعمه جميع المترجمات المستهدفة.

جاهزية الفريق: تتطلب الميزات الجديدة التعلم على مستوى الفريق. الميزة التي يفهمها مطور واحد فقط تؤدي إلى اختناق الصيانة.

النقاط الرئيسية
  • يتيحstd::expectedمعالجة الأخطاء على نمط الصدأ من خلال التسلسل الأحادي (.and_then()،.transform())
  • std::print/std::printlnيستبدلstd::cout <<بمخرجات منسقة نظيفة وسريعة وآمنة من النوع
  • تضيف نطاقات C++23 طرق عرضzipوchunkوslideوcartesian_productوenumerate
  • يؤدي استنتاج هذا إلى التخلص من تكرار التحميل الزائد لوظيفة العضو const/non-const
  • يستهدف C++26 العقود والانعكاس الثابت ومطابقة الأنماط وstd::executionلعدم المزامنة
  • اعتماد معايير جديدة بشكل تدريجي — التحقق من دعم المترجم، واختباره بدقة، والتأكد من جاهزية الفريق

اختبار — اختبر معرفتك

(15 نقطة خبرة)

1. ماذا يمثل`std::expected<int, Error>`؟

2. ماذا ينتج`std::views::zip(names, scores)`؟

3. ما هي ميزة C++26 التي تسمح بتحديد الشروط المسبقة للوظيفة والشروط اللاحقة؟