تنفيذ الوظائف في وقت الترجمة

في مجال الحوسبة ، يُقصد بتنفيذ الدالة في وقت الترجمة (أو تقييم الدالة في وقت الترجمة ، أو التعبيرات الثابتة العامة ) قدرة المُترجم ، الذي يُترجم عادةً الدالة إلى لغة الآلة ويُنفذها في وقت التشغيل ، على تنفيذ الدالة في وقت الترجمة . ويكون ذلك ممكنًا إذا كانت وسائط الدالة معروفة في وقت الترجمة، ولم تُشر الدالة إلى أي حالة عامة أو تُحاول تعديلها (أي أنها دالة خالصة ).

إذا كانت قيمة بعض الوسائط فقط معروفة، فقد يظل المترجم قادرًا على تنفيذ مستوى معين من تنفيذ وظائف وقت الترجمة ( التقييم الجزئي )، مما قد ينتج عنه رمز أكثر كفاءة مما لو لم تكن أي وسائط معروفة.

أمثلة

التلعثم

يُعد نظام الماكرو في لغة Lisp مثالاً مبكراً على استخدام التقييم في وقت الترجمة للوظائف المعرفة من قبل المستخدم في نفس اللغة.

لغة سي++

كان امتداد Metacode للغة C++ (Vandevoorde 2003) [ 1 ] نظامًا تجريبيًا مبكرًا للسماح بتقييم الوظائف في وقت الترجمة (CTFE) وحقن التعليمات البرمجية كصيغة محسنة لبرمجة القوالب C++ .

في الإصدارات السابقة من لغة C++ ، كان يُستخدم غالبًا برمجة القوالب الوصفية لحساب القيم في وقت الترجمة، كما يلي. في لغتي C وC++، كانت تُستخدم التعدادات تاريخيًا لإنشاء ثوابت عددية صحيحة مُسماة في وقت الترجمة (بدون وحدات ماكرو للمعالج المسبق )، قبل إضافة constexpr في C++11 .

template < unsigned int N > struct Factorial { enum { VALUE = N * Factorial < N - 1 >:: VALUE }; };template <> struct Factorial < 0 > { enum { VALUE = 1 }; };void foo () { int x = Factorial < 0 >:: VALUE ; // == 1 int y = Factorial < 4 >:: VALUE ; // == 24 }

باستخدام تقييم الدالة في وقت الترجمة، سيكون الكود المستخدم لحساب المضروب مشابهًا لما يكتبه المرء لتقييم وقت التشغيل، على سبيل المثال باستخدام constexpr.

استيراد std ؛constexpr unsigned int factorial ( unsigned int n ) noexcept { return n ? ( n * factorial ( n - 1 )) : 1 ; }constexpr unsigned int f10 = factorial ( 10 );int main () { std :: println ( "{}" , f10 ); return 0 ; }

في لغة C++11، تُعرف هذه التقنية باسم التعبيرات الثابتة المعممة ( constexpr). [ 2 ] أما لغة C++14 فتخفف القيود المفروضة على constexpr، مما يسمح بالتصريحات المحلية واستخدام العبارات الشرطية والحلقات (يبقى القيد العام المتمثل في توفر جميع البيانات المطلوبة للتنفيذ في وقت الترجمة).

// حساب مضروب العدد بشكل تكراري أثناء الترجمة. constexpr unsigned int factorial ( unsigned int n ) noexcept { unsigned int result = 1 ; while ( n > 1 ) { result *= n -- ; } return result ; }int main () { constexpr int f4 = factorial ( 4 ); // f4 == 24 }

في C++20 ، تم تقديم الدوال الفورية باستخدام الكلمة المفتاحية consteval ، وأصبح تنفيذ الدوال في وقت الترجمة أكثر سهولة ومرونة مع تخفيف القيود.constexpr

// حساب مضروب العدد بشكل تكراري أثناء الترجمة. consteval unsigned int factorial ( unsigned int n ) noexcept { unsigned int result = 1 ; while ( n > 1 ) { result *= n -- ; } return result ; }int main () { int f4 = factorial ( 4 ); // f4 == 24 }

بما أن الدالة factorialمُعلَّمة consteval، فمن المضمون استدعاؤها في وقت الترجمة دون الحاجة إلى سياق تقييم ثابت آخر. لذا، يوفر استخدام الدوال الفورية تطبيقات واسعة في البرمجة الوصفية والتحقق أثناء الترجمة. على سبيل المثال، يُستخدم هذا في [ 3 ]std::format() .

void assertionFailed () {}consteval void testAssert ( bool cond ) { if ( ! cond ) { assertionFailed (); } }consteval void test () { int x = 10 ; testAssert ( x == 10 ); // صحيح x ++ ; testAssert ( x == 11 ); // صحيح x -- ; testAssert ( x == 12 ); // فشل هنا }int main () { test (); }

في هذا المثال، تفشل عملية الترجمة لأن الدالة المباشرة استدعت دالة أخرى غير قابلة للاستخدام في التعبيرات الثابتة. بعبارة أخرى، تتوقف عملية الترجمة بعد فشل التأكيد.

ستظهر رسالة خطأ التجميع النموذجية التالية:

في الدالة ' int main () ' : في توسيع ' constexpr ' للدالة ' test () وفي توسيع ' constexpr ' للدالة ' testAssert ( x == 12 ) ' ، خطأ : استدعاء دالة غير ' constexpr ' هي ' assertionFailed () assertionFailed (); ~~~~~~~~~~~~~~~^~ [ ... ]

هذا مثال على استخدام الدوال المباشرة كدوال بانية، للتحقق من الوسائط في وقت الترجمة:

استيراد std ؛باستخدام std :: string_view ؛void assertionFailed () {}struct CheckedMessage { string_view msg ;consteval CheckedMessage ( const char msg []) : msg { msg } { if ( msg . ends_with ( '!' )) { assertionFailed (); } } };void sendCalmMessage ( const CheckedMessage & arg ) { std :: println ( "{}" , arg . msg ); }int main () { sendCalmMessage ( "Hello, world" ); sendCalmMessage ( "Hello, world!" ); }

فشلت عملية التجميع هنا مع ظهور الرسالة التالية:

في الدالة ' int main () ' : في توسيع ' constexpr ' للدالة ' CheckedMessage ( "Hello, world!" ) خطأ : استدعاء دالة غير ' constexpr ' هي ' void assertionFailed ( ) ' assertionFailed (); ~~~~~~~~~~~~~~~^~ [ ... ]

د

هذا مثال على تقييم الدالة في وقت الترجمة في لغة البرمجة D : [ 4 ]

uint factorial ( uint n ) { if ( n == 0 ) { return 1 ; } return n * factorial ( n - 1 ); }// تم حسابها في وقت الترجمة enum y = factorial ( 0 ); // == 1 enum x = factorial ( 4 ); // == 24

يُحدد هذا المثال دالة D صالحة تُسمى "factorial"، والتي تُقيّم عادةً أثناء وقت التشغيل. يُخبر استخدام enumالمُصرّف بأن مُهيئ المتغيرات يجب حسابه أثناء وقت الترجمة. لاحظ أنه يجب أن تكون وسائط الدالة قابلة للحل أثناء وقت الترجمة أيضًا. [ 5 ]

يمكن استخدام CTFE لملء هياكل البيانات في وقت الترجمة بطريقة بسيطة (إصدار D 2):

uint [] genFactorials ( uint n ) { uint [ ] result = new uint [ n ] ; result [ 0 ] = 1 ; foreach ( i ; 1..n ) { result [ i ] = result [ i - 1 ] * i ; } return result ; }enum factorials = genFactorials ( 13 );void main () {}// يحتوي 'factorials' في وقت الترجمة على: // [1, 1, 2, 6, 24, 120, 720, 5_040, 40_320, 362_880, 3_628_800, // 39_916_800, 479_001_600]

يمكن استخدام CTFE لإنشاء سلاسل يتم تحليلها وتجميعها بعد ذلك كشفرة D في لغة D.

الصدأ

هذا مثال على تقييم الدوال في وقت الترجمة في لغة Rust . يتم ذلك باستخدام [ 6 ] .constfn

const fn add ( a : i32 , b : i32 ) -> i32 { a + b }const RESULT : i32 = add ( 10 , 5 );fn main () { // القيمة 15 مُضمنة بالفعل في النظام الثنائي هنا println! ( "النتيجة هي: {}" , RESULT ); }

زيج

هذا مثال على تقييم الدالة في وقت الترجمة في Zig . [ 7 ]

دالة عامة لحساب مضروب العدد ( n : حجم الاستخدام ) حجم الاستخدام { متغير النتيجة = 1 ؛ من أجل ( 1 ..( n + 1 )) | i | { النتيجة *= i ؛ } إرجاع النتيجة ؛ }دالة عامة رئيسية () void { const x = comptime factorial ( 0 ); // == 0 const y = comptime factorial ( 4 ); // == 24 }

يُحدد هذا المثال دالة Zig صالحة تُسمى، factorial()والتي عادةً ما تُقيّم أثناء التشغيل. يُخبر استخدام comptimeالمُصرّف أن مُهيئ المتغيرات يجب حسابه أثناء الترجمة. لاحظ أنه يجب أيضًا أن تكون وسائط الدالة قابلة للحل أثناء الترجمة.

يدعم Zig أيضًا معلمات وقت الترجمة. [ 8 ]

دالة عامة لحساب مضروب العدد ( زمن الحساب n : حجم الاستخدام ) حجم الاستخدام { متغير النتيجة : حجم الاستخدام = 1 ; من أجل ( 1 ..( n + 1 )) | i | { النتيجة *= i ; } إرجاع النتيجة ; }pub fn main () void { const x = factorial ( 0 ); // == 0 const y = factorial ( 4 ); // == 24 }

يمكن استخدام CTFE لإنشاء هياكل بيانات عامة في وقت الترجمة:

دالة List ( comptime T : type ) type { return struct { items : [] T , len : usize , }; }// يمكن إنشاء بنية بيانات List العامة بتمرير نوع: var buffer : [ 10 ] i32 = undefined ; var list = List ( i32 ){ . items = & buffer , . len = 0 , };

مراجع

  1. ^ ديفيد فانديفورد، مجموعة إديسون للتصميم (18 أبريل 2003). “البرمجة الفوقية العاكسة في C++” (PDF) . تم الاسترجاع 19 يوليو، 2015 .
  2. غابرييل دوس ريس وبيارن ستروستروب (مارس 2010). "تعبيرات ثابتة عامة للغات برمجة الأنظمة. SAC-2010. الندوة الخامسة والعشرون لجمعية الحوسبة التطبيقية" (PDF) .
  3. cppreference.com (10 مايو 2026). "std::format" . cppreference.com . cppreference.com.
  4. مواصفات لغة D 2.0: الدوال
  5. مواصفات لغة D 2.0: السمات
  6. فريق Rust (16 أبريل 2026). "التقييم المستمر" . docs.rust-lang.org . فريق Rust.
  7. مرجع لغة Zig 0.11.0: تعابير وقت الترجمة
  8. مرجع لغة Zig 0.11.0: معلمات وقت الترجمة