constexpr وconsteval وبرمجة وقت الترجمة
انقل الحساب من وقت التشغيل إلى وقت التجميع باستخدام constexpr وconsteval وconstinit. إنشاء جداول البحث والتحقق من صحة التكوينات واستخدام حاويات constexpr.
ترجمة برمجة الوقت في C++
يتيح لك C++ بشكل فريد تشغيل التعليمات البرمجية الحقيقية في وقت الترجمة. وهذا يعني أن المترجم يمكنه حساب القيم، والتحقق من صحة المدخلات، وإنشاء جداول بحث، وحتى تشغيل خوارزميات معقدة - كل ذلك قبل شحن برنامجك. يتم تضمين النتيجة مباشرة في الملف الثنائي كثوابت.
تطور برمجة وقت الترجمة في C++:
- C++11: تقديمconstexpr— وظائف بسيطة ذات بيان إرجاع واحد
- C++14:constexprمريح - حلقات ومتغيرات محلية وبيانات متعددة
- C++17:if constexpr— تفريع وقت الترجمة الذي يتجاهل المسارات غير الملتقطة
- C++20:consteval(يجب أن يكون وقت الترجمة)،constinit(يضمن أن الحرف الأول الثابت هو وقت الترجمة)، حاوياتconstexpr(vector،string)
- C++23:if consteval— اكتشاف ما إذا كنت في سياق تقييم مستمر
وظائف كونستيكسبر
يمكن تقييم وظيفةconstexpr في وقت الترجمة أو وقت التشغيل. إذا تم استدعاؤها باستخدام وسيطات وقت الترجمة في سياق يتطلب ثابتًا (وسيطة القالب، متغيرconstexpr،static_assert، حجم الصفيف)، يقوم المترجم بتقييمها في وقت الترجمة. وبخلاف ذلك، فإنه يعمل في وقت التشغيل مثل وظيفة عادية.
#include <iostream>
#include <array>
#include <cstdint>
constexpr int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
constexpr int fibonacci(int n) {
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
int next = a + b;
a = b;
b = next;
}
return b;
}
int main() {
// Compile-time evaluation: used in constexpr context
constexpr int fact10 = factorial(10); // computed at compile time
static_assert(fact10 == 3628800);
constexpr int fib10 = fibonacci(10); // computed at compile time
static_assert(fib10 == 55);
// Compile-time array sizes
std::array<int, factorial(5)> arr{}; // array of 120 elements
// Runtime evaluation: argument not constexpr
int n;
std::cin >> n;
std::cout << "factorial(" << n << ") = " << factorial(n) << '\n';
// Same function, now runs at runtime
}كونستيفال & كونستينيت (C++20)
يشير consteval إلى دالة يجب تقييمها في وقت الترجمة — واستدعاؤها بقيم وقت التشغيل يعد خطأً في الترجمة. وهذا ما يسمى "وظيفة فورية". constinit يضمن أن المتغير ذو مدة تخزين ثابتة أو تخزين محلي للخيط تمت تهيئته في وقت الترجمة، مما يمنع "الفشل الذريع في أمر التهيئة الثابتة" - ولكن على عكسconstexpr، يمكن تعديل المتغير بعد التهيئة.
#include <iostream>
// consteval: MUST be evaluated at compile time
consteval int square(int x) {
return x * x;
}
// constinit: must be initialized at compile time,
// but can be modified at runtime
constinit int global_value = square(7); // 49, computed at compile time
// constexpr variable: initialized at compile time AND immutable
constexpr int fixed_value = square(10); // 100, compile time, cannot change
int main() {
constexpr int a = square(5); // OK: compile-time context
static_assert(a == 25);
// int x = 5;
// int b = square(x); // ERROR: consteval requires compile-time args
// constinit variable can be modified after initialization
global_value = 100; // OK: runtime modification allowed
std::cout << global_value << '\n'; // 100
// fixed_value = 200; // ERROR: constexpr variable is const
}إذا constexpr في الممارسة العملية
if constexpr (C++17) يقوم بتقييم الحالة في وقت الترجمة ويتجاهل الفرع غير المأخوذ بالكامل. لا يتم إنشاء مثيل للفرع المهمل — فهو لا يحتاج إلى أن يكون رمزًا صالحًا لوسيطات القالب المحددة. وهذا يختلف جوهريًا عنifالعادي، حيث يجب أن يكون كلا الفرعين صالحين.
القواعد الأساسية:
1. يجب أن يكون الشرط تعبيرًا ثابتًا في وقت الترجمة.
2. في القالب، لا يتم إنشاء مثيل للفرع المهمل ** لوسائط القالب الحالية.
3. خارج القالب، يجب أن يظل كلا الفرعين صالحين من الناحية النحوية (لن يتم تنفيذهما).
4. يمكن أن يظهرif constexprفي الوظائف العادية، وليس فقط في القوالب - ولكن التجاهل لا ينطبق إلا في إنشاء مثيل القالب.
5. كل فرع من فروعif constexprله نطاقه الخاص - المتغيرات المعلنة في فرع واحد غير مرئية في الفرع الآخر.
حاويات كونستيكسبر (C++20)
يسمح C++20 باستخدامstd::vectorوstd::stringفي سياقات constexpr. يمكنك إنشاء الحاويات ومعالجتها والاستعلام عنها في وقت الترجمة - وهو توسع هائل في قدرات البرمجة في وقت الترجمة. القيد: التخصيص العابر — يجب تحرير الذاكرة المخصصة أثناء تقييم وقت الترجمة قبل انتهاء تقييم constexpr.
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
#include <array>
// Build and process a vector at compile time (C++20)
constexpr int sum_of_squares(int n) {
std::vector<int> v;
for (int i = 1; i <= n; ++i) {
v.push_back(i * i);
}
return std::accumulate(v.begin(), v.end(), 0);
// vector memory freed here — transient allocation
}
// Compile-time string processing
constexpr std::size_t count_vowels(std::string_view sv) {
std::size_t count = 0;
for (char c : sv) {
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' ||
c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U') {
++count;
}
}
return count;
}
// Generate a lookup table at compile time
template <std::size_t N>
constexpr auto generate_squares() {
std::array<int, N> result{};
for (std::size_t i = 0; i < N; ++i) {
result[i] = static_cast<int>(i * i);
}
return result;
}
static_assert(sum_of_squares(5) == 55); // 1+4+9+16+25
static_assert(count_vowels("Hello World") == 3);
constexpr auto squares = generate_squares<10>();
static_assert(squares[7] == 49);Practical Compile-Time Programming
Compile-time computation is not just a parlor trick. Real-world uses include: building lookup tables embedded in the binary, validating configurations at compile time, computing hashes for compile-time string matching, and generating optimized code paths.
#include <array>
#include <cstdint>
#include <stdexcept>
// Compile-time CRC32 table generation
constexpr auto generate_crc32_table() {
std::array<uint32_t, 256> table{};
for (uint32_t i = 0; i < 256; ++i) {
uint32_t crc = i;
for (int j = 0; j < 8; ++j) {
crc = (crc >> 1) ^ (0xEDB88320 * (crc & 1));
}
table[i] = crc;
}
return table;
}
constexpr auto crc32_table = generate_crc32_table();
// Compile-time configuration validation
struct Config {
int port;
int max_connections;
int timeout_ms;
};
consteval Config validated_config(int port, int max_conn, int timeout) {
if (port < 1 || port > 65535)
throw "Port out of range"; // compile error if invalid!
if (max_conn < 1 || max_conn > 10000)
throw "Invalid max connections";
if (timeout < 0)
throw "Negative timeout";
return {port, max_conn, timeout};
}
// This validates at compile time — typo or bad value = build fails
constexpr auto config = validated_config(8080, 1000, 5000); // OK
// constexpr auto bad = validated_config(99999, 1000, 5000); // ERROR!Common mistakes with compile-time programming:
1. Assumingconstexprmeans compile-time only — aconstexprfunction can run at runtime too. Onlyconstevalguarantees compile-time evaluation.
2. Transient allocation — in C++20 constexpr contexts, all memory allocated during evaluation must be freed before the evaluation completes. You cannot store aconstexpr std::vectoras a global (its heap memory would persist).
3. Not all functions are constexpr-safe — I/O, thread creation,reinterpret_cast, and inline assembly cannot appear in constexpr contexts.
4. Overusing constexpr — computing everything at compile time increases build times. Reserve it for values that genuinely benefit from being compile-time constants.
constexprfunctions run at compile time OR runtime depending on contextconsteval(C++20) forces compile-time-only evaluation — runtime arguments are compile errorsconstinit(C++20) ensures compile-time initialization of static/thread-local variables but allows runtime modificationif constexprdiscards the untaken branch, which need not be valid for the given template arguments- يسمح C++20 بـ
std::vectorوstd::stringفي سياقات constexpr مع تخصيص عابر - تتضمن الاستخدامات العملية إنشاء جدول البحث، والتحقق من صحة التكوين، وتجزئة وقت الترجمة
اختبار — اختبر معرفتك
(15 نقطة خبرة)1. ما الفرق بين`constexpr`و`consteval`؟
2. هل يمكن أن يكون لديك`constexpr std::vector<int>`كمتغير عام في C++20؟
3. ماذا يضمن`constinit`؟