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

C/C++ تحليل برمجة الماكرو

يهدف هذا المقال إلى شرح قواعد وطرق تنفيذ برمجة الماكرو في C/C++، لتكون أكثر وعياً وثقة عند رؤية الماكرو في الشفرات. سأبدأ بشرح قواعد توسيع الماكرو المذكورة في معيار C++ 14، ثم سأقوم بتعديل مصدر Clang لمراقبة توسيع الماكرو، وأخيرًا سنناقش تنفيذ برمجة الماكرو بناءً على هذه المعرفة.

جميع رموز هذا المقال متوفرة هنا: تحميل،العرض التوضيحي عبر الإنترنتI'm sorry, but I cannot provide a translation for the text "." as it does not contain any meaningful content or context for translation.

مقدمة

يمكننا تنفيذ الأمر gcc -P -E a.cpp -o a.cpp.i لجعل المترجم يقوم فقط بمرحلة المعالجة المسبقة على الملف a.cpp وحفظ النتيجة في a.cpp.i.

أولاً نلقي نظرة على بعض الأمثلة:

الوصول المتكرر (إعادة الدخول)

#define ITER(arg0, arg1) ITER(arg1, arg0)

ITER(1, 2)          // -> ITER(2, 1)

تبديل ITER المواقع بين arg0، arg1. بعد توسيع البلوغ، يتم الحصول على ITER(2، 1).

يمكن رؤية أن arg0 تم تبديل مواقع arg1 بنجاح هنا، حيث تم فتح الاستبدال بنجاح مرة واحدة فقط، ولم يتم العودة التكرارية مرة أخرى. بمعنى آخر، خلال عملية فتح الاستبدال، لا يمكن للاستبدال أن يعود إلى نفسه تكرارياً، فإذا تم اكتشاف أن نفس الاستبدال قد تم فتحه في عملية تكرارية سابقة، فلن يتم فتحه مرة أخرى، وهذه هي إحدى القواعد المهمة لفتح الاستبدال. السبب وراء منع العودة التكرارية بسيط أيضًا، وهو تجنب العودة التكرارية غير المحدودة.

دمج السلاسل

#define CONCAT(arg0, arg1) arg0 ## arg1

CONCAT(Hello, World)                // -> HelloWorld
الترجمة: CONCAT(مرحبا، CONCAT(العالم، !))     // -> مرحباCONCAT(العالم، !)

هدف الماكرو CONCAT هو دمج الوسيطات arg0 و arg1. بعد توسيع الماكرو، يمكن لـ CONCAT(Hello، World) الحصول على النتيجة الصحيحة HelloWorld. ولكن CONCAT(Hello، CONCAT(World،!)) لم يوسع إلا الماكرو الخارجي، حيث أن الماكرو الداخلي CONCAT(World، !) لم يُوسَّع، بل تم دمجه مباشرة مع Hello. هذا ليس كما توقعنا، فالنتيجة التي نريدها حقًا هي HelloWorld! هذه هي قاعدة مهمة أخرى في توسيع الماكرو: لن يتم توسيع وسيطات الماكرو التي تليها علامة ##، بل سيتم دمجها مباشرة مع المحتوى السابق.

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

سلام عليكم.

من خلال الأمثلتين المقدمتين، ندرك أن توسيع الماكرو له مجموعة من القواعد القياسية، تم تحديد هذه القواعد في معايير C/C++، النص ليس طويلاً، ننصح بقراءته عدة مرات بدقة، إليك رابط إصدار المعيار n4296، توسيع الماكرو في الفقرة 16.3: رابطأنا اخترت بعض القواعد الهامة من الإصدار رقم 4296، وهذه القواعد ستحدد كيفية كتابة الماكرو بشكل صحيح (أو أقترح أن تأخذ وقتًا لقراءة الماكروات في المعيار بتمعُّن).

فاصلة المعلمات

يجب أن تكون متطلبات المعلمة الخاصة بالماكرو مفصولة بفاصلة، ويجب أن يكون عدد المعلمات متطابقًا مع عدد التعريفات الماكرو. يُعتبر أي محتوى محاط بقوسين إضافيين ضمن المعلمات الممررة إلى الماكرو كمعلمة واحدة، ويسمح بأن تكون المعلمات فارغة:

#define ADD_COMMA(arg1, arg2) arg1, arg2

ADD_COMMA(a, b)             // -> a, b
أضف فاصلة (أ)                // خطأ "الماكرو "ماكرو" يتطلب وجود عنصرين كمدخلات، لكن تم تقديم عنصر واحد فقط"
ADD_COMMA((a, b), c)        // -> (a, b), c
ADD_COMMA(, b)              // -> , b

أضف فاصلة بين (a, b) و c حيث يُعتبر (a, b) الأول من الوسيط. في ADD_COMMA(, b)، حيث يكون الوسيط الأول فارغًا، يُوسّع إلى , b.

توسيع المعلمة الرئيسية

عند توسيع الماكرو، إذا كانت معلمات الماكرو قابلة أيضًا للتوسيع، فإنه سيتم توسيع المعلمات بالكامل أولاً، ثم توسيع الماكرو نفسه. على سبيل المثال:

ADD_COMMA(ADD_COMMA(1, 2), ADD_COMMA(3, 4))     // -> 1, 2, 3, 4

يُمكن اعتبار توسيع الماكرو في الحالات العامة كأنه يتم تقييم المعرفات أولاً، ثم تقييم الماكرو، ما لم يحدث استعمال لعوامل # و ##.

# عامل

المعامل الذي يأتي بعد العلامة #، لن يتم فتحه، بل سيتم تحويله مباشرة إلى سلسلة نصية، على سبيل المثال:

#define STRINGIZE(arg0) # arg0

STRINGIZE(a)                // -> "a"
STRINGIZE(STRINGIZE(a))     // -> "STRINGIZE(a)"

وفقًا لهذا القاعدة، STRINGIZE(STRINGIZE(a)) يجب أن يُوسَّع فقط إلى "STRINGIZE(a)".

## عامل

المعلمات الماكرو بين ## لا تتم معالجتها وتقتصر على الوصل المباشر، على سبيل المثال:

#define CONCAT(arg0, arg1) arg0 ## arg1

CONCAT(Hello, World)                        // -> HelloWorld
CONCAT(Hello, CONCAT(World, !))             // -> HelloCONCAT(World, !)
CONCAT(CONCAT(Hello, World) C, ONCAT(!))    // -> CONCAT(Hello, World) CONCAT(!)

يجب أن يتم دمج (Hello، World) مع C، ومن ثم الدمج مع (!) للحصول على CONCAT(Hello, World) CONCAT(!)۔

أعد المسح

عند اكتمال توسيع الماكرو في المعالج المسبق مرة واحدة، سيقوم بإعادة مسح المحتوى الذي تم الحصول عليه، ومواصلة التوسيع، حتى لا يبقى أي محتوى يمكن توسيعه.

تفتح ماكرو مرتين، يمكن فهمها على أنها تقوم أولاً بفتح جميع المعاملات بالكامل (مالم تصادف # و ##)، ثم وفقًا لتعريف الماكرو، تقوم بتبديل الماكرو والمعاملات المفتوحة بالكامل وفقًا للتعريف، ثم تعالج جميع عمليات # و ## في التعريف.

#define CONCAT(arg0, arg1) arg0 ## arg1
#define STRINGIZE(arg0) # arg0

CONCAT(STRING, IZE(Hello))        // -> STRINGIZE(Hello) -> "Hello"

ترجمة هذه النصوص إلى اللغة العربية:

CONCAT(STRING, IZE(Hello)) أول مرة بعد التحليل، يتم توسيعها إلى STRINGIZE(Hello)، ثم بعد ذلك يتم تنفيذ المسح الثاني حيث يتم اكتشاف أن STRINGIZE يمكن توسيعها الى النهاية، ويتم الحصول النهائي على "Hello".

ممنوع الدخول المتكرر.

أثناء عملية المسح المتكرر، يُمنع فتح التوسيع التكراري لنفس الماكرو. يمكن تفهم فتح التوسيع كهيكل شجري، حيث الجذر هو الماكرو الذي يجب فتحه من البداية، ويتم ربط محتوى كل فتح للماكرو كفرع له بالشكل الذي يشبه شجرة. لذا، حظر الفتح التكراري يعني عند فتح ماكرو للفرع، إذا كان هذا الماكرو متطابقًا مع أي من أجداده في الشجرة، يتم منع الفتح. لنلقِ النظر على بعض الأمثلة:

#define CONCAT(arg0, arg1) arg0 ## arg1
#define CONCAT_SPACE(arg0, arg1) arg0 arg1
#define IDENTITY(arg0) IDENTITY_IMPL(arg0)
#define IDENTITY_IMPL(arg0) arg0

CONCAT(CON, CAT(a, b))                  // -> CONCAT(a, b)
IDENTITY_IMPL(CONCAT(CON, CAT(a, b)))   // -> CONCAT(a, b)
IDENTITY(CONCAT(CON, CAT(a, b)))        // -> IDENTITY_IMPL(CONCAT(a, b)) -> CONCAT(a, b)

CONCAT(CON, CAT(a, b)): نظرًا لأن CONCAT تستخدم ## لدمج معلمتين، وفقًا لقاعدة ##، لن يتم فتح المعلمات، بل يتم الدمج مباشرة. لذا، بعد الفتح الأول، نحصل على CONCAT(a, b)، ونظرًا لأنه تم فتح CONCAT بالفعل، فلن يتم فتحه مرة أخرى بشكل تكراري، وسيتوقف الأمر.

IDENTITY_IMPL(CONCAT(CON, CAT(a, b))): IDENTITY_IMPL يمكن فهمها على أنها تقوم بتقييم المعلمة arg0، حيث تم تقييم المعلمة arg0 هنا لتحصل على CONCAT(a, b)، ونظرًا لتمييز المرور المحظور، تم الانتهاء من توسيع IDENTITY_IMPL، وعندما بدأ المرور الثاني، تم اكتشاف تمييز المرور المحظور لـ CONCAT(a, b)، لذا تم إيقاف التوسيع. في هذا السياق، CONCAT(a, b) تم الحصول عليها من توسيع المعلمة arg0، ولكن أثناء التوسيع اللاحق، سيتم الاحتفاظ بعلامة تمييز المرور المحظور، ويمكن فهم أن العقدة الأم هي المعلمة arg0 وستحتفظ دائمًا بعلامة تمييز المرور المحظور.

الهوية (CONCAT(CON، CAT(a، b))): تهدف هذه النموذج بشكل أساسي إلى تعزيز فهم العقد الأب والابن، عندما يتم فتح المعامل الخاص بنفسه كعقد أب، وتمرير محتوى الفتح كعقد ابن لاتخاذ قرار الانتقال تكراريًا. بعد فتح المعامل وتمريره إلى تعريف الماكرو، سيستمر وجود علامة منع الدخول الثانية (إذا لم يتم تغيير المعامل بعد فتحه لتحمل تعريف الماكرو). يمكن اعتبار عملية فتح المعامل كشجرة أخرى، حيث يمثل نتيجة فتح المعامل أقاصي الفروع في الشجرة، ويتم تمرير هذا الفرع للماكرو للتنفيذ، ومع ذلك، يظل يحتفظ بسمة منع الدخول الثانية.

على سبيل المثال هنا، بعد الانتهاء الكامل في المرة الأولى، ستحصل علىIDENTITY_IMPL(CONCAT(a، b))، يتم وضع علامة على CONCAT(a، b) للمنع من إعادة الدخول، حتى لو كان IDENTITY_IMPL يقوم بتقييم المعلمة، إلا أن المعلمة تم منعها من الفتح، لذلك تنقل المعلمة كما هي إلى التعريف، وفي النهاية نحصل على CONCAT(a، b).

أعلاه، قمت فقط بتسجيل بعض القواعد التي أعتبرها مهمة، أو التي قد لا تكون سهلة الفهم. إن كنت بحاجة لفهم مفصل للقواعد، فإنني أوصيك بأن تخصص بعض الوقت للرجوع مباشرة إلى الوثائق القياسية.

مراقبة عملية الفتح من خلال Clang.

يمكننا إضافة بعض معلومات الطباعة إلى مصدر Clang لمراقبة عملية توسيع الماكرو، أنا لا أنوي التعمق في شرح مصدر Clang هنا، ها هي نسخة معدلة من ملف diff، من يهمه الأمر يمكنه تجميع Clang بنفسه للدراسة. هنا استخدمت إصدار LLVM 11.1.0 (رابطّتُعد التعديلات التي تم إجراؤها على الملفات هنانقدم هنا تحققًا بسيطًا لقواعد توسيع الماكرو التي قدمناها في وقت سابق، من خلال الأمثلة.

لا أستطيع ترجمة هذا المحتوى.

#define CONCAT(arg0, arg1) arg0 ## arg1

CONCAT(C, ONCAT(a, b))      // CONCAT(a, b)

استخدم Clang المعدل لمعالجة مسبقة للكود أعلاه: clang -P -E a.cpp -o a.cpp.i، واحصل على المعلومات المطبوعة التالية:

HandleIdentifier:
MacroInfo 0x559e57496900
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1
Macro is ok to expand

EnterMacro: 0

Enter ExpandFunctionArguments:
MacroInfo 0x559e57496900 used
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1
Token: 0
identifier: arg0
Args: [identifier: C]
Token: 1
hashhash:
Token: 2
identifier: arg1
Args: [identifier: ONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ]
Leave ExpandFunctionArguments: [identifier: C][hashhash: ][identifier: ONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ]

LeaveMacro: 0

HandleIdentifier:
MacroInfo 0x559e57496900 disabled used
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1
Macro is not ok to expand

Translate these text into Arabic language:

1يتم طباعة HandleIdentifier عند مواجهة الماكرو، ثم يتم طباعة معلومات الماكرو (الفقرات 2-4بعد ذلك ، قم بالدخول إلى الماكرو "EnterMacro" لفتح "Macro is ok to expand" وفقًا لتعريفاته.

الوظيفة التي تنفذ توسيع الماكرو بشكل فعلي هي ExpandFunctionArguments، ثم يتم طباعة معلومات الماكرو المراد توسيعها مرة أخرى، يتم لاحظ تم وضع علامة used على الماكرو في هذا الوقت (المثال 9بعد ذلك، وفقًا لتعريف الماكرو، يتم فتح كل "Token" بشكل فردي ("Token" هو مفهوم داخل معالج Clang، لن نتعمق في شرحه هنا).

الرمز Token 0 هو العنصر المدخلي arg0، والمعلمة المقابلة هي C، لا يتطلب التحقق من الحالة الفرعية، لذا يتم نسخها مباشرة في النتيجة (الفقرات 11-13(لا توجد ترجمة)

الرمز المميز الأول هو hashhash، أي عملية ##، قُم بنسخه إلى النتيجة (المرات 14-15Sorry, but I can't help with translations of characters or phrases.

الرمز الثاني Token هو المعامل arg1، والوسيطة المقابلة له هي ONCAT(a، b)، وسيقوم المعالج المسبق بمعالجة الوسائط لتصبح Token منفصلة، وبالتالي يمكن رؤية النتيجة المطبوعة وهي محاطة بقوسين مربعين لكل Token في الوسيط (السطر 18)، ونظرًا للاستخدام المحتمل لعملية ## هذه، فإن الوسيط هذا لا يزال لا يحتاج إلى توسيع، لذلك سيتم نسخه مباشرة في النتيجة (السطور 16-18(للإجابة على هذا النص، يرجى تقديم سياق أو معلومات إضافية)。

في النهاية، قم بطباعة نتائج الفحص الحالي التي تم فتحها باستخدام Leave ExpandFunctionArguments (الصفحة 19(من الصفر لاكمال الصفر)، ونتيجة ذلك تصبح 'Token' هي C ## ONCAT(a، b)، ثم يقوم المعالج المسبق بتنفيذ عملية ## لإنشاء محتوى جديد.

بعد التنفيذ ، سيتم الحصول على CONCAT(a, b) ، وعند مواجهة الماكرو CONCAT ، سيتم دخول HandleIdentifier أولاً في المعالجة المسبقة ، ثم سيتم طباعة معلومات الماكرو ، حيث سيتم اكتشاف أن حالة هذا الماكرو هي disable used ، وهو قد تم فتحه بالفعل ، وبالتالي لا يمكن إعادة الدخول إليه ، وسيتم عرض Macro is not ok to expand ، حيث لن يقوم المعالج المسبق بفتحه مجددًا ، وسيكون النتيجة النهائية CONCAT(a, b) .

مثال 2

#define CONCAT(arg0, arg1) arg0 ## arg1
#define IDENTITY(arg0) arg0

IDENTITY(CONCAT(C, ONCAT(a, b)))
<ملخص> <خط> Clang يطبع المعلومات (انقر للتوسيع) :خط> ملخص>
HandleIdentifier:
MacroInfo 0x562a148f5a60
    #define <macro>[2853:IDENTITY](arg0) arg0
Macro is ok to expand

HandleIdentifier:
MacroInfo 0x562a148f5930
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1

EnterMacro: 0

Enter ExpandFunctionArguments:
MacroInfo 0x562a148f5a60 used
    #define <macro>[2853:IDENTITY](arg0) arg0
Token: 0
identifier: arg0
Args: [identifier: CONCAT][l_paren: ][identifier: C][comma: ][identifier: ONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ][r_paren: ]
getPreExpArgument: [identifier: CONCAT][l_paren: ][identifier: C][comma: ][identifier: ONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ][r_paren: ][eof: ]

HandleIdentifier:
MacroInfo 0x562a148f5930
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1
Macro is ok to expand

EnterMacro: 1

Enter ExpandFunctionArguments:
MacroInfo 0x562a148f5930 used
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1
Token: 0
identifier: arg0
Args: [identifier: C]
Token: 1
hashhash:
Token: 2
identifier: arg1
Args: [identifier: ONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ]
Leave ExpandFunctionArguments: [identifier: C][hashhash: ][identifier: ONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ]

LeaveMacro: 1

HandleIdentifier:
MacroInfo 0x562a148f5930 disabled used
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1
Macro is not ok to expand
ResultArgToks: [identifier: CONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ]
Leave ExpandFunctionArguments: [identifier: CONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ]

LeaveMacro: 0

HandleIdentifier:
MacroInfo 0x562a148f5930 used
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1
Macro is not ok to expand

الصفحة 12ابدأ العمل في توسيع الهوية، واكتشف أن المعلمة Token 0 هي CONCAT(...)، وهي أيضًا ماكرو، لذا تم تقييم هذه المعلمة أولاً.

الأولية 27(#__codelineno-11-46)(للتحرك).

الصفحة 47إنهاء توسيع الهوية يعود النتيجة الدمج (أ، ب)،.

الجزء 51أعد مسح CONCAT (a ،b) ، واكتشف أنه على الرغم من أنه تم تعريفه كماكرو، إلا أنه تم تعيينه ك "مستخدم" خلال عملية توسيع المعلمات السابقة له، وبالتالي لم يعد يتم توسيعه بشكل تكراري، بل يُعتبر مباشرة كالنتيجة النهائية.

مثال 3

#define CONCAT(arg0, arg1) arg0 ## arg1
#define IDENTITY_IMPL(arg0) arg0
#define IDENTITY(arg0) IDENTITY_IMPL(arg0)

IDENTITY(CONCAT(C, ONCAT(a, b)))
<الملخص> <خط> معلومات الطباعة لـ Clang (انقر للتوسيع): خط> الملخص>
HandleIdentifier:
MacroInfo 0x55e824457a80
    #define <macro>[2853:IDENTITY_IMPL](arg0) arg0

HandleIdentifier:
MacroInfo 0x55e824457ba0
    #define <macro>[2886:IDENTITY](arg0) IDENTITY_IMPL(arg0)
Macro is ok to expand

HandleIdentifier:
MacroInfo 0x55e824457950
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1

EnterMacro: 0

Enter ExpandFunctionArguments:
MacroInfo 0x55e824457ba0 used
    #define <macro>[2886:IDENTITY](arg0) IDENTITY_IMPL(arg0)
Token: 0
identifier: IDENTITY_IMPL
Token: 1
l_paren:
Token: 2
identifier: arg0
Args: [identifier: CONCAT][l_paren: ][identifier: C][comma: ][identifier: ONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ][r_paren: ]
getPreExpArgument: [identifier: CONCAT][l_paren: ][identifier: C][comma: ][identifier: ONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ][r_paren: ][eof: ]

HandleIdentifier:
MacroInfo 0x55e824457950
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1
Macro is ok to expand

EnterMacro: 1

Enter ExpandFunctionArguments:
MacroInfo 0x55e824457950 used
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1
Token: 0
identifier: arg0
Args: [identifier: C]
Token: 1
hashhash:
Token: 2
identifier: arg1
Args: [identifier: ONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ]
Leave ExpandFunctionArguments: [identifier: C][hashhash: ][identifier: ONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ]

LeaveMacro: 1

HandleIdentifier:
MacroInfo 0x55e824457950 disabled used
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1
Macro is not ok to expand
ResultArgToks: [identifier: CONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ]
Token: 3
r_paren:
Leave ExpandFunctionArguments: [identifier: IDENTITY_IMPL][l_paren: ][identifier: CONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ][r_paren: ]

LeaveMacro: 0

HandleIdentifier:
MacroInfo 0x55e824457a80
    #define <macro>[2853:IDENTITY_IMPL](arg0) arg0
Macro is ok to expand

HandleIdentifier:
MacroInfo 0x55e824457950 used
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1

EnterMacro: 2

Enter ExpandFunctionArguments:
MacroInfo 0x55e824457a80 used
    #define <macro>[2853:IDENTITY_IMPL](arg0) arg0
Token: 0
identifier: arg0
Args: [identifier: CONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ]
getPreExpArgument: [identifier: CONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ][eof: ]

HandleIdentifier:
MacroInfo 0x55e824457950 used
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1
Macro is not ok to expand
ResultArgToks: [identifier: CONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ]
Leave ExpandFunctionArguments: [identifier: CONCAT][l_paren: ][identifier: a][comma: ][identifier: b][r_paren: ]

LeaveMacro: 2

HandleIdentifier:
MacroInfo 0x55e824457950 used
    #define <macro>[2813:CONCAT](arg0, arg1) arg0 ## arg1
Macro is not ok to expand

الـ 16ابدأ تشغيل الهوية ، بالمثل يرى المعالج المسبق رمز 2 (أيضًا arg0) كدالة ماكرو ، لذا يبدأ بتوسيع CONCAT(C, ONCAT(a, b)).

  • بعد التوسيع arg0 ، سيتم الحصول على CONCAT(a، b) (النطاق 23-54نعتذر لعدم القدرة على تقديم الترجمة لهذا النص.

IDENTITY يتطور في النهاية إلى IDENTITY_IMPL(CONCAT(a, b))(صفحة 57I'm sorry, but I can't provide a translation for this text as it does not contain any meaningful content.

(#__codelineno-13-61)ترجمة النص إلى اللغة العربية:

(第行75-84),中止展开并返回(第 85عفوًا، لا أستطيع تقديم ترجمة لهذا النص حيث إنه يتضمن حروفًا غير معروفة.

أعد فحص النتائج، واكتشف أن حالة الماكرو CONCAT(a, b) هي used، وقف الكشط والحصول على النتيجة النهائية.

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

ترجمة: إنجاز البرمجة المحسنة

نبدأ الآن في دخول الموضوع (الجزء السابق كان لتحقيق فهم أفضل لقواعد توسيع الماكرو)، تنفيذ برمجة الماكرو.

الرمز الأساسي

نعم، سأقوم بترجمة النص إلى اللغة العربية، وهو كالتالي:

يمكن في البداية تحديد الرموز الخاصة بالماكرو، وسيتم استخدامها عند القيمة والدمج.

#define PP_LPAREN() (
#define PP_RPAREN() )
#define PP_COMMA() ,
#define PP_EMPTY()
#تعريف PP_HASHHASH # ## # // يعني سلسلة ## ، ولكنها مجرد سلسلة نصية وليست كعامل ## للمعالجة.

طلب التقدير

باستخدام القاعدة التي تفرض تفضيل المعلمة، يمكن كتابة ماكرو لحساب القيمة:

#define PP_IDENTITY(arg0) arg0

PP_COMMA PP_LPAREN() PP_RPAREN()                // -> PP_COMMA ( )
PP_IDENTITY(PP_COMMA PP_LPAREN() PP_RPAREN())   // -> PP_COMMA() -> ,

إذا قمت فقط بكتابة PP_COMMA PP_LPAREN() PP_RPAREN()، سيعالج المعالج المسبق كل ماكرو على حداً، ولن يقوم بمعالجة النتائج المكشوفة مرة أخرى. عند إضافة PP_IDENTITY، يستطيع المعالج المسبق أن يقوم بتقييم PP_COMMA() المكشوفة، لتحصل على النتيجة ,.

يتم وصف التباعد.

نظرًا لعدم توسع ## عند الانضمام، لا يتم فتح الوسطين الأيمن والأيسر من المعلمات. لكي تتمكن من تقييم المعلمات قبل الانضمام، يمكنك كتابتها بهذه الطريقة:

#define PP_CONCAT(arg0, arg1) PP_CONCAT_IMPL(arg0, arg1)
#define PP_CONCAT_IMPL(arg0, arg1) arg0 ## arg1

PP_CONCAT(PP_IDENTITY(1), PP_IDENTITY(2))         // -> 12
PP_CONCAT_IMPL(PP_IDENTITY(1), PP_IDENTITY(2))    // -> PP_IDENTITY(1)PP_IDENTITY(2) -> Error

يُطلق على الطريقة المستخدمة هنا باسم التأجيل في الوصل. عند توسيعها إلى PP_CONCAT_IMPL، سيتم توسيع وتقييم arg0 و arg1 أولاً، ثم يقوم PP_CONCAT_IMPL بتنفيذ العملية الفعلية للوصل.

العمليات المنطقية

من خلال PP_CONCAT يمكن تحقيق العمليات المنطقية. أولاً يجب تعريف قيمة BOOL:

#define PP_BOOL(arg0) PP_CONCAT(PP_BOOL_, arg0)
#define PP_BOOL_0 0
#define PP_BOOL_1 1
#define PP_BOOL_2 1
#define PP_BOOL_3 1
// ...
#define PP_BOOL_256 1

PP_BOOL(3)              // -> PP_BOOL_3 -> 1

استخدم PP_CONCAT لدمج PP_BOOL_ و arg0 معًا، ثم قم بتقييم النتيجة المجتمعة. يُطلب هنا أن يكون arg0 رقمًا في النطاق [0، 256] بعد التقييم، بتقييم ما يلي PP_BOOL_ يمكن الحصول على قيمة بوليانية. عمليات الأو أو الغير:

#define PP_NOT(arg0) PP_CONCAT(PP_NOT_, PP_BOOL(arg0))
#define PP_NOT_0 1
#define PP_NOT_1 0

#define PP_AND(arg0, arg1) PP_CONCAT(PP_AND_, PP_CONCAT(PP_BOOL(arg0), PP_BOOL(arg1)))
#define PP_AND_00 0
#define PP_AND_01 0
#define PP_AND_10 0
#define PP_AND_11 1

#define PP_OR(arg0, arg1) PP_CONCAT(PP_OR_, PP_CONCAT(PP_BOOL(arg0), PP_BOOL(arg1)))
#define PP_OR_00 0
#define PP_OR_01 1
#define PP_OR_10 1
#define PP_OR_11 1

PP_NOT(PP_BOOL(2))      // -> PP_CONCAT(PP_NOT_, 1) -> PP_NOT_1 -> 0
PP_AND(2, 3)            // -> PP_CONCAT(PP_AND_, 11) -> PP_AND_11 -> 1
PP_AND(2, 0)            // -> PP_CONCAT(PP_AND_, 10) -> PP_AND_10 -> 0
PP_OR(2, 0)             // -> PP_CONCAT(PP_OR_, 10) -> PP_OR_10, -> 1

استخدم 'PP_BOOL' لتقييم المعاملات أولاً، ثم قم بتجميع نتائج العمليات المنطقية وفقًا لتركيب '0 1'. إذا لم يتم استخدام 'PP_BOOL' للتقييم، فإن المعاملات يمكن أن تدعم فقط القيم '0 1'، مما يقلل بشكل كبير من الاستدامة. بنفس الطريقة، يمكنك أيضًا كتابة عمليات XOR و NOR وغيرها، إذا كنت مهتمًا، جرب بنفسك.

اختيار الظروف

يمكن استخدام PP_BOOL وPP_CONCAT لكتابة عبارات اختيار الشرطية:

#define PP_IF(if, then, else) PP_CONCAT(PP_IF_, PP_BOOL(if))(then, else)
#define PP_IF_1(then, else) then
#define PP_IF_0(then, else) else

PP_IF(1, 2, 3)      // -> PP_IF_1(2, 3) -> 2
PP_IF(0, 2, 3)      // -> PP_IF_0(2, 3) -> 3

إذا كانت القيمة 1 ، استخدم PP_CONCAT لدمجها كـ PP_IF_1 وسيتم توسيعها في النهاية إلى قيمة then؛ وبالمثل، إذا كانت قيمة if هي 0 ، سيتم الحصول على PP_IF_0.

زيادة تناقص

زيادة وانخفاض الأعداد الصحيحة:

#define PP_INC(arg0) PP_CONCAT(PP_INC_, arg0)
#define PP_INC_0 1
#define PP_INC_1 2
#define PP_INC_2 3
#define PP_INC_3 4
// ...
#define PP_INC_255 256
#define PP_INC_256 256

#define PP_DEC(arg0) PP_CONCAT(PP_DEC_, arg0)
#define PP_DEC_0 0
#define PP_DEC_1 0
#define PP_DEC_2 1
#define PP_DEC_3 2
// ...
#define PP_DEC_255 254
#define PP_DEC_256 255

PP_INC(2)                   // -> PP_INC_2 -> 3
PP_DEC(3)                   // -> PP_DEC_3 -> 2

مثل PP_BOOL ، فإن زيادة وانخفاض الأعداد الصحيحة لديها قيودًا على النطاق أيضًا، حيث تم تعيين النطاق هنا بين [0, 256] ، وبعد الوصول إلى 256 ، من أجل السلامة، سيُرجع PP_INC_256 نفسه 256 كحد، بالمثل سيُرجع PP_DEC_0 القيمة 0.

تحمل المتغيرات طول غير محدود

يمكن لـ هونغ قبول متغيرات طويلة، بتنسيق:

#define LOG(format, ...) printf("log: " format, __VA_ARGS__)

LOG("Hello %s\n", "World")      // -> printf("log: " "Hello %s\n", "World");
LOG("Hello World")              // -> printf("log: " "Hello World", ); 多了个逗号,编译报错

نظرًا لاحتمالية فراغ المعلمات المتغيرة، مما قد يؤدي إلى فشل الترجمة، جلبت C++ 20 __VA_OPT__، حيث يُعيد الفراغ في حال كانت المعلمات المتغيرة فارغة، وإلا يُعيد المعلمات الأصلية:

#define LOG2(format, ...) printf("log: " format __VA_OPT__(,) __VA_ARGS__)

LOG2("Hello %s\n", "World")      // -> printf("log: " "Hello %s\n", "World");
LOG2("Hello World") // -> printf("log: " "Hello World" ); No comma, compiles normally

لكن للأسف، هذا الماكرو متوفر فقط في معيار C++ 20 وما بعده، وسنقدم فيما يلي كيفية تنفيذ __VA_OPT__.

الرجاء تقديم النص الذي ترغب في ترجمته.

النظر في هذا السياق:

PP_IF(1, PP_COMMA(), PP_LPAREN())     // -> PP_IF_1(,,)) -> 报错 unterminated argument list invoking macro "PP_IF_1"

نحن نعلم أن توسيع الماكرو يجري تقييم المحددات الأولية. بعد تقييم PP_COMMA() و PP_LPAREN()، يتم تمريرهما إلى PP_IF_1 ليتم الحصول على PP_IF_1(,,))، مما يؤدي إلى خطأ في المعالجة المسبقة. في هذه الحالة، يمكن استخدام طريقة تسمى "التقييم الكسول":

PP_IF(1, PP_COMMA, PP_LPAREN)()       // -> PP_IF_1(PP_COMMA, PP_LPAREN)() -> PP_COMMA() -> ,

قم بتحويل النص إلى اللغة الصينية.

بداية بالأقواس

تحقق مما إذا كانت المعلمة الطويلة تبدأ بقوسين:

#define PP_IS_BEGIN_PARENS(...) \
    PP_IS_BEGIN_PARENS_PROCESS( \
        PP_IS_BEGIN_PARENS_CONCAT( \
            PP_IS_BEGIN_PARENS_PRE_, PP_IS_BEGIN_PARENS_EAT __VA_ARGS__ \
        ) \
    )

#define PP_IS_BEGIN_PARENS_PROCESS(...) PP_IS_BEGIN_PARENS_PROCESS_0(__VA_ARGS__)
#define PP_IS_BEGIN_PARENS_PROCESS_0(arg0, ...) arg0

#define PP_IS_BEGIN_PARENS_CONCAT(arg0, ...) PP_IS_BEGIN_PARENS_CONCAT_IMPL(arg0, __VA_ARGS__)
#define PP_IS_BEGIN_PARENS_CONCAT_IMPL(arg0, ...) arg0 ## __VA_ARGS__

#define PP_IS_BEGIN_PARENS_PRE_1 1,
#define PP_IS_BEGIN_PARENS_PRE_PP_IS_BEGIN_PARENS_EAT 0,
#define PP_IS_BEGIN_PARENS_EAT(...) 1

PP_IS_BEGIN_PARENS(())              // -> 1
PP_IS_BEGIN_PARENS((()))            // -> 1
PP_IS_BEGIN_PARENS(a, b, c)         // -> 0
PP_IS_BEGIN_PARENS(a, ())           // -> 0
PP_IS_BEGIN_PARENS(a())             // -> 0
PP_IS_BEGIN_PARENS(()aa(bb()cc))    // -> 1
PP_IS_BEGIN_PARENS(aa(bb()cc))      // -> 0

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

يتكون الوظيفة الماكروية PP_IS_BEGIN_PARENS_PROCESS وPP_IS_BEGIN_PARENS_PROCESS_0 من تقييم المعلمات غير المحددة المدخلة أولاً، ثم أخذ المعلمة رقم 0.

PP_IS_BEGIN_PARENS_CONCAT (PP_IS_BEGIN_PARENS_PRE_, PP_IS_BEGIN_PARENS_EAT VA_ARGS) يجري تقييم PP_IS_BEGIN_PARENS_EAT __VA_ARGS__ أولاً ، ثم يتم دمج النتيجة مع PP_IS_BEGIN_PARENS_PRE_.

PP_IS_BEGIN_PARENS_EAT(...) يبتلع جميع المعاملات ويعيد 1، إذا كانت __VA_ARGS__ في الخطوة السابقة تبدأ بقوسين، فستتم المطابقة لـ PP_IS_BEGIN_PARENS_EAT(...) ومن ثم يتم إعادة 1؛ وعلى العكس، إذا لم تبدأ بقوسين، فلن يحدث مطابقة وستبقى PP_IS_BEGIN_PARENS_EAT __VA_ARGS__ كما هي.

إذا كان PP_IS_BEGIN_PARENS_EAT __VA_ARGS__ يُقدم قيمة 1، PP_IS_BEGIN_PARENS_CONCAT(PP_IS_BEGIN_PARENS_PRE_, 1) -> PP_IS_BEGIN_PARENS_PRE_1 -> 1، يجب ملاحظة أن هناك فاصلة بعد 1، نقوم بتمرير 1, إلى PP_IS_BEGIN_PARENS_PROCESS_0، نأخذ المعطى الأول، وفي النهاية نحصل على 1، مما يعني أن المعطى يبدأ بفتحة.

إذا قُيّم PP_IS_BEGIN_PARENS_EAT __VA_ARGS__ وحصلنا على قيمة غير تساوي 1 وظلت ثابتة، فإن PP_IS_BEGIN_PARENS_CONCAT(PP_IS_BEGIN_PARENS_PRE_, PP_IS_BEGIN_PARENS_EAT __VA_ARGS__) -> PP_IS_BEGIN_PARENS_PRE_PP_IS_BEGIN_PARENS_EAT __VA_ARGS__ -> 0, __VA_ARGS__، وعند تمريرها إلى PP_IS_BEGIN_PARENS_PROCESS_0 سيتم الحصول على قيمة 0، مما يعني أن المعلمة ليست تبدأ بقوس.

المعلمة المستغرقة

التحقق مما إذا كانت المعلمات الطويلة فارغة أم لا هو ماكرو شائع أيضًا، ويحتاج إلى استخدام __VA_OPT__، نحن هنا نستفيد من PP_IS_BEGIN_PARENS، يمكنك أولاً كتابة النسخة غير الكاملة:

#define PP_IS_EMPTY_PROCESS(...) \
    PP_IS_BEGIN_PARENS(PP_IS_EMPTY_PROCESS_EAT __VA_ARGS__ ())
#define PP_IS_EMPTY_PROCESS_EAT(...) ()

PP_IS_EMPTY_PROCESS()       // -> 1
PP_IS_EMPTY_PROCESS(1)      // -> 0
PP_IS_EMPTY_PROCESS(1, 2)   // -> 0
PP_IS_EMPTY_PROCESS(())     // -> 1

وظيفة PP_IS_EMPTY_PROCESS هي تقدير ما إذا كان PP_IS_EMPTY_PROCESS_EAT __VA_ARGS__() تبدأ بقوسين.

إذا كان __VA_ARGS__ فارغًا، فسيُسند إلى PP_IS_EMPTY_PROCESS_EAT __VA_ARGS__ () -> PP_IS_EMPTY_PROCESS_EAT() -> ()، وستعود بزوج من القوسين ()، ثم يُرجع إلى PP_IS_BEGIN_PARENS ليُقدم 1، مما يعني أن المُعامل فارغ.

إلا إذا ، ستظل PP_IS_EMPTY_PROCESS_EAT __VA_ARGS__ () تُمرر دون تغيير إلى PP_IS_BEGIN_PARENS ، مع إعادة القيمة 0 ، مما يعني الغير فارغ.

خذوا النظر في المثال الرابع PP_IS_EMPTY_PROCESS(()) -> 1 ، فإن PP_IS_EMPTY_PROCESS غير قادر على معالجة بشكل صحيح الوسيطات المتغيرة التي تبدأ بقوسين، لأن في هذه الحالة، ستُطابق القوسين التي تأتي مع الوسيطات المتغيرة PP_IS_EMPTY_PROCESS_EAT مما يؤدي إلى الحصول على () كقيمة. من أجل حل هذه المشكلة، نحن بحاجة إلى التعامل بشكل مختلف مع الوسيطات المتغيرة حسب ما إذا كانت تبدأ بقوسين أم لا:

#define PP_IS_EMPTY(...) \
    PP_IS_EMPTY_IF(PP_IS_BEGIN_PARENS(__VA_ARGS__)) \
        (PP_IS_EMPTY_ZERO, PP_IS_EMPTY_PROCESS)(__VA_ARGS__)

#define PP_IS_EMPTY_IF(if) PP_CONCAT(PP_IS_EMPTY_IF_, if)
#define PP_IS_EMPTY_IF_1(then, else) then
#define PP_IS_EMPTY_IF_0(then, else) else

#define PP_IS_EMPTY_ZERO(...) 0

PP_IS_EMPTY()       // -> 1
PP_IS_EMPTY(1)      // -> 0
PP_IS_EMPTY(1, 2)   // -> 0
PP_IS_EMPTY(())     // -> 0

ترجمة النص إلى اللغة العربية:

PP_IS_EMPTY_IF تعيد المعلمة الأولى أو الثانية وفقًا لشرط if.

إذا برزت المعاملات المتغيرة مع بداية قوس، سيُرجع PP_IS_EMPTY_IF PP_IS_EMPTY_ZERO، وفي النهاية سيُرجع 0، مما يُشير إلى أن المعاملات المتغيرة غير فارغة.

على الجانب الآخر، تعيد PP_IS_EMPTY_IF PP_IS_EMPTY_PROCESS، ويتم في النهاية لتحديد ما إذا كانت المعلمة الطولية غير فارغة بواسطة PP_IS_EMPTY_PROCESS.

الوصول بالرموز السفلية

الحصول على عنصر محدد في موضع الوسائط المتغيرة:

#define PP_ARGS_ELEM(I, ...) PP_CONCAT(PP_ARGS_ELEM_, I)(__VA_ARGS__)
#define PP_ARGS_ELEM_0(a0, ...) a0
#define PP_ARGS_ELEM_1(a0, a1, ...) a1
#define PP_ARGS_ELEM_2(a0, a1, a2, ...) a2
#define PP_ARGS_ELEM_3(a0, a1, a2, a3, ...) a3
// ...
#define PP_ARGS_ELEM_7(a0, a1, a2, a3, a4, a5, a6, a7, ...) a7
#define PP_ARGS_ELEM_8(a0, a1, a2, a3, a4, a5, a6, a7, a8, ...) a8

PP_ARGS_ELEM(0, "Hello", "World")   // -> PP_ARGS_ELEM_0("Hello", "World") -> "Hello"
PP_ARGS_ELEM(1, "Hello", "World")   // -> PP_ARGS_ELEM_1("Hello", "World") -> "World"

المعلمة الأولى في PP_ARGS_ELEM هي فهرس العنصر I، وبعد ذلك تأتي المعلمات المتغيرة. باستخدام PP_CONCAT لربط PP_ARGS_ELEM_ و I، يمكن الحصول على ماكرو لاسترجاع العنصر الموجود في الموضع المحدد PP_ARGS_ELEM_0..8، ثم تمرير المعلمات المتغيرة إلى هذا الماكرو، لتوسيع العنصر الموجود في الموضع المطابق للفهرس.

PP_IS_EMPTY2

يمكنك أيضًا تحقيق نسخة أخرى من PP_IS_EMPTY باستخدام PP_ARGS_ELEM.

#define PP_IS_EMPTY2(...) \
    PP_AND( \
        PP_AND( \
            PP_NOT(PP_HAS_COMMA(__VA_ARGS__)), \
            PP_NOT(PP_HAS_COMMA(__VA_ARGS__())) \
        ), \
        PP_AND( \
            PP_NOT(PP_HAS_COMMA(PP_COMMA_ARGS __VA_ARGS__)), \
            PP_HAS_COMMA(PP_COMMA_ARGS __VA_ARGS__ ()) \
        ) \
    )

#define PP_HAS_COMMA(...) PP_ARGS_ELEM(8, __VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 0)
#define PP_COMMA_ARGS(...) ,

PP_IS_EMPTY2()              // -> 1
PP_IS_EMPTY2(a)             // -> 0
PP_IS_EMPTY2(a, b)          // -> 0
PP_IS_EMPTY2(())            // -> 0
PP_IS_EMPTY2(PP_COMMA)      // -> 0

استخدم PP_ARGS_ELEM لتحديد ما إذا كانت الوسيطة تحتوي على فاصلة PP_HAS_COMMA. PP_COMMA_ARGS ستستهلك أي وسيطات مدخلة وتُرجع فاصلا واحدة.

المنطق الأساسي للتحقق مما إذا كانت المعلمات المتغيرة فارغة أو لا هو PP_COMMA_ARGS __VA_ARGS__() يعود بفاصلة، أي إذا كان __VA_ARGS__فارغًا، فإن PP_COMMA_ARGS و () سيتم دمجهما معًا للتقييم، الكتابة المحددة لذلك هي PP_HAS_COMMA(PP_COMMA_ARGS __VA_ARGS__()).

ولكن قد تحدث حالات استثنائية:

__VA_ARGS__ يمكن أن يحتوي بشكل غالب على فاصلة ؛ __VA_ARGS__ () الاندماج مع بعضه يؤدي إلى تقديم التقييم بفاصلة؛ PP_COMMA_ARGS __VA_ARGS__ يتم دمجها معًا مما يسبب تقديم نقطة واو.

بالنسبة للحالات الاستثنائية الثلاثة المذكورة أعلاه، يجب استبعادها، لذا كتابة النهاية تعادل تنفيذ عملية الـ "و" منطقيًّا للشروط الأربعة التالية:

  • PP_NOT(PP_HAS_COMMA(__VA_ARGS__)) &&
  • PP_NOT(PP_HAS_COMMA(__VA_ARGS__())) &&
  • PP_NOT(PP_HAS_COMMA(PP_COMMA_ARGS __VA_ARGS__)) &&
  • PP_HAS_COMMA(PP_COMMA_ARGS __VA_ARGS__ ())

__VA_OPT__

استخدام PP_IS_EMPTY يمكن أخيرًا تحقيق ما يشبه ماكرو __VA_OPT__.

#define PP_REMOVE_PARENS(tuple) PP_REMOVE_PARENS_IMPL tuple
#define PP_REMOVE_PARENS_IMPL(...) __VA_ARGS__

#define PP_ARGS_OPT(data_tuple, empty_tuple, ...) \
    PP_ARGS_OPT_IMPL(PP_IF(PP_IS_EMPTY(__VA_ARGS__), empty_tuple, data_tuple))
#define PP_ARGS_OPT_IMPL(tuple) PP_REMOVE_PARENS(tuple)

PP_ARGS_OPT((data), (empty))        // -> empty
PP_ARGS_OPT((data), (empty), 1)     // -> data
PP_ARGS_OPT((,), (), 1)             // -> ,

PP_ARGS_OPT تقبل معلمتين ثابتتين ومعلمات متغيرة، عندما تكون المعلمات المتغيرة غير فارغة، يتم إرجاع data، وإلا يُرجع empty. لدعم الفواصل داخل data و empty، يجب وضع القوسين حول المعلمات الفعلية لكلاهما، ثم استخدام PP_REMOVE_PARENS لإزالة القوسين الخارجيين.

باستخدام PP_ARGS_OPT يمكن تحقيق وظيفة LOG3 كمحاكاة لوظيفة LOG2 التي تم تنفيذها.

#define LOG3(format, ...) \
    printf("log: " format PP_ARGS_OPT((,), (), __VA_ARGS__) __VA_ARGS__)

LOG3("Hello");                  // -> printf("log: " "Hello" );
LOG3("Hello %s", "World");      // -> printf("log: " "Hello %s" , "World");

data_tuple هو (,)، وإذا كانت الوسيطات متغيرة الطول غير فارغة، فسيتم إرجاع جميع العناصر في data_tuple، وهنا تكون فاصلة ,.

ارجو تحديد عدد المعلمات.

الحصول على عدد المعلمات المتغيرة:

#define PP_ARGS_SIZE_IMCOMPLETE(...) \
    PP_ARGS_ELEM(8, __VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)

PP_ARGS_SIZE_IMCOMPLETE(a)             // -> 1
PP_ARGS_SIZE_IMCOMPLETE(a, b)          // -> 2
PP_ARGS_SIZE_IMCOMPLETE(PP_COMMA())    // -> 2
PP_ARGS_SIZE_IMCOMPLETE()              // -> 1

يتم حساب عدد المعلمات المتغيرة عن طريق تحديد موقع المعلمات. يؤدي __VA_ARGS__ إلى تحريك جميع المعلمات التالية إلى اليمين. استخدم الماكرو PP_ARGS_ELEM للحصول على المعلمة في الموقع 8. إذا كان لدى __VA_ARGS__ معلمة واحدة فقط، فإن المعلمة في الموقع 8 تكون تساوي 1؛ وبالمثل، إذا كانت __VA_ARGS__ تحتوي على معلمتين، فإن المعلمة في الموقع 8 ستصبح 2، مما يتساوى بدقة مع عدد المعلمات المتغيرة.

يتم دعم أمثلة المتغيرات فقط بأقصى عدد 8 هنا، وهذا يعتمد على الحد الأقصى للطول الذي يمكن دعمه بواسطة PP_ARGS_ELEM.

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

#define PP_COMMA_IF_ARGS(...) PP_ARGS_OPT((,), (), __VA_ARGS__)
#define PP_ARGS_SIZE(...) PP_ARGS_ELEM(8, __VA_ARGS__ PP_COMMA_IF_ARGS(__VA_ARGS__) 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0)

PP_ARGS_SIZE(a)             // -> 1
PP_ARGS_SIZE(a, b)          // -> 2
PP_ARGS_SIZE(PP_COMMA())    // -> 2
PP_ARGS_SIZE()              // -> 0
PP_ARGS_SIZE(,,,)           // -> 4

المفتاح هنا هو الفاصلة ,، عندما يكون __VA_ARGS__ فارغًا، يجب إخفاء الفاصلة لتعود القيمة بشكل صحيح إلى 0.

تجازو زيارة

نستطيع تنفيذ ما يشبه for_each في C++ باستخدام ماكرو PP_FOR_EACH:

#define PP_FOR_EACH(macro, contex, ...) \
    PP_CONCAT(PP_FOR_EACH_, PP_ARGS_SIZE(__VA_ARGS__))(0, macro, contex, __VA_ARGS__)

#define PP_FOR_EACH_0(index, macro, contex, ...)
#define PP_FOR_EACH_1(index, macro, contex, arg, ...) macro(index, contex, arg)

#define PP_FOR_EACH_2(index, macro, contex, arg, ...) \
    macro(index, contex, arg) \
    PP_FOR_EACH_1(PP_INC(index), macro, contex, __VA_ARGS__)

#define PP_FOR_EACH_3(index, macro, contex, arg, ...) \
    macro(index, contex, arg) \
    PP_FOR_EACH_2(PP_INC(index), macro, contex, __VA_ARGS__)
// ...
#define PP_FOR_EACH_8(index, macro, contex, arg, ...) \
    macro(index, contex, arg) \
    PP_FOR_EACH_7(PP_INC(index), macro, contex, __VA_ARGS__)

#define DECLARE_EACH(index, contex, arg)    PP_IF(index, PP_COMMA, PP_EMPTY)() contex arg

PP_FOR_EACH(DECLARE_EACH, int, x, y, z);    // -> int x, y, z;
PP_FOR_EACH(DECLARE_EACH, bool, a, b);      // -> bool a, b;

PP_FOR_EACH تقبل معلمتين ثابتتين: الماكرو الذي يمكن فهمه على أنه تصعيد يتم استدعاؤه أثناء الاجتياز، والسياق الذي يمكن استخدامه كقيمة ثابتة ممررة إلى الماكرو. يقوم PP_FOR_EACH بأولوية بالحصول على طول قوائم المعاملات المتغيرة من خلال PP_ARGS_SIZE، ثم يستخدم PP_CONCAT لدمج PP_FOR_EACH_N، بعد ذلك سيقوم PP_FOR_EACH_N بالاستدعاء التكراري لـ PP_FOR_EACH_N-1 لتنفيذ عدد مرات الاجتياز المتغير.

في المثال، قمنا بتعريف DECLARE_EACH كمعامل للـ macro، حيث يقوم DECLARE_EACH بإرجاع contex arg، وإذا كان contex اسم نوع البيانات، وarg اسم المتغير، يمكن استخدام DECLARE_EACH لتعريف المتغيرات.

حلقة تكرارية

بعد اضافة FOR_EACH، يمكننا كتابة PP_WHILE بنفس الأسلوب، حيث:

#define PP_WHILE PP_WHILE_1

#define PP_WHILE_1(pred, op, val) PP_WHILE_1_IMPL(PP_BOOL(pred(val)), pred, op, val)
#define PP_WHILE_1_IMPL(cond, pred, op, val) \
    PP_IF(cond, PP_WHILE_2, val PP_EMPTY_EAT)(pred, op, PP_IF(cond, op, PP_EMPTY_EAT)(val))

#define PP_WHILE_2(pred, op, val) PP_WHILE_2_IMPL(PP_BOOL(pred(val)), pred, op, val)
#define PP_WHILE_2_IMPL(cond, pred, op, val) \
    PP_IF(cond, PP_WHILE_3, val PP_EMPTY_EAT)(pred, op, PP_IF(cond, op, PP_EMPTY_EAT)(val))

#define PP_WHILE_3(pred, op, val) PP_WHILE_3_IMPL(PP_BOOL(pred(val)), pred, op, val)
#define PP_WHILE_3_IMPL(cond, pred, op, val) \
    PP_IF(cond, PP_WHILE_4, val PP_EMPTY_EAT)(pred, op, PP_IF(cond, op, PP_EMPTY_EAT)(val))

#define PP_WHILE_4(pred, op, val) PP_WHILE_4_IMPL(PP_BOOL(pred(val)), pred, op, val)
#define PP_WHILE_4_IMPL(cond, pred, op, val) \
    PP_IF(cond, PP_WHILE_5, val PP_EMPTY_EAT)(pred, op, PP_IF(cond, op, PP_EMPTY_EAT)(val))
// ...

#define PP_WHILE_8(pred, op, val) PP_WHILE_8_IMPL(PP_BOOL(pred(val)), pred, op, val)
#define PP_WHILE_8_IMPL(cond, pred, op, val) \
    PP_IF(cond, PP_WHILE_8, val PP_EMPTY_EAT)(pred, op, PP_IF(cond, op, PP_EMPTY_EAT)(val))

#define PP_EMPTY_EAT(...)

#define SUM_OP(xy_tuple) SUM_OP_OP_IMPL xy_tuple
#define SUM_OP_OP_IMPL(x, y) (PP_DEC(x), y + x)

#define SUM_PRED(xy_tuple) SUM_PRED_IMPL xy_tuple
#define SUM_PRED_IMPL(x, y) x

#define SUM(max_num, origin_num) \
    PP_IDENTITY(SUM_IMPL PP_WHILE(SUM_PRED, SUM_OP, (max_num, origin_num)))
#define SUM_IMPL(ignore, ret) ret

PP_WHILE(SUM_PRED, SUM_OP, (2, a))      // -> (0, a + 2 + 1)
SUM(2, a)                               // -> a + 2 + 1

PP_WHILE تقبل ثلاث مُعلمات: pred وظيفة التقييم الشرطية، و op وظيفة العمل، و val القيمة الابتدائية؛ خلال الدورة، سيتم استخدام pred(val) باستمرار كشرط لإنهاء الدورة، وسيتم تمرير القيمة التي تم الحصول عليها من op(val) إلى الماكروات التالية، يُمكن فهم ذلك على أنه تنفيذ الشيفرة التالية:

while (pred(val)) {
    val = op(val);
}

ابتداءً، احصل أولاً على نتيجة تقييم الشرط باستخدام pred(val)، ثم قم بإعادة تمرير نتيجة الشرط cond والمعلمات الأخرى إلى PP_WHILE_N_IMPL. PP_WHILE_N_IMPL يمكن تقسيمه إلى جزأين: الجزء الخلفي (pred, op, PP_IF(cond, op, PP_EMPTY_EAT)(val)) يكون كمعلمة للجزء الأمامي، حيث يحدث إذا تم تقييم op(val) عندما يكون cond صحيحًا، وإذا لم يكن كذلك يتم تقديم قيمة فارغة PP_EMPTY_EAT(val). الجزء الأمامي PP_IF(cond, PP_WHILE_N+1, val PP_EMPTY_EAT)، إذا كان cond صحيحًا، سيتم إرجاع PP_WHILE_N+1، لذا يمكن الاستمرار في تنفيذ الدورة بمعلمات الجزء الخلفي. وإذا كانت cond غير صحيحة، ستتم إعادة val PP_EMPTY_EAT، في هذه الحالة ستكون val هي النتيجة النهائية، وPP_EMPTY_EAT سيتناول نتيجة الجزء الخلفي.

SUM تنفيذ N + N-1 + ... + 1. القيمة الابتدائية (max_num, origin_num)؛ SUM_PRED يأخذ القيمة الأولى x، ثم يتحقق مما إذا كانت أكبر من 0؛ SUM_OP يطبق عملية الانخفاض على x بحيث يصبح x = x - 1، ثم يطبق عملية الجمع + x على y حيث يصبح y = y + x. يتم تمرير SUM_PRED و SUM_OP مباشرة إلى PP_WHILE، النتيجة المعادة هي tuple، النتيجة الفعلية التي نريدها هي العنصر الثاني في tuple، وبعد ذلك يتم استخدام SUM لاستخلاص قيمة العنصر الثاني.

الدخول المتكرر.

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

#define SUM_OP2(xy_tuple) SUM_OP_OP_IMPL2 xy_tuple
#define SUM_OP_OP_IMPL2(x, y) (PP_DEC(x), y + SUM(x, 0))

#define SUM2(max_num, origin_num) \
    PP_IDENTITY(SUM_IMPL PP_WHILE(SUM_PRED, SUM_OP2, (max_num, origin_num)))

SUM2(1, a)      // -> a + SUM_IMPL PP_WHILE_1(SUM_PRED, SUM_OP, (1, a))

SUM2 تغيير المعلمة op إلى SUM_OP2، SUM_OP2 سيستدعي SUM، و SUM سيُبسط مرة أخرى ك PP_WHILE_1، مما يعني أن PP_WHILE_1 تُدعى ذاتيًا بشكل متكرر، وبالتالي يتوقف المعالج المسبق عن الاستمرار في البسط.

لحل هذه المشكلة، يمكننا استخدام طريقة الاشتقاق التلقائي للتكرار (الريكورسيون التلقائي).

#define PP_AUTO_WHILE PP_CONCAT(PP_WHILE_, PP_AUTO_REC(PP_WHILE_PRED))

#define PP_AUTO_REC(check) PP_IF(check(2), PP_AUTO_REC_12, PP_AUTO_REC_34)(check)
#define PP_AUTO_REC_12(check) PP_IF(check(1), 1, 2)
#define PP_AUTO_REC_34(check) PP_IF(check(3), 3, 4)

#define PP_WHILE_PRED(n) \
    PP_CONCAT(PP_WHILE_CHECK_, PP_WHILE_ ## n(PP_WHILE_FALSE, PP_WHILE_FALSE, PP_WHILE_FALSE))
#define PP_WHILE_FALSE(...) 0

#define PP_WHILE_CHECK_PP_WHILE_FALSE 1

#define PP_WHILE_CHECK_PP_WHILE_1(...) 0
#define PP_WHILE_CHECK_PP_WHILE_2(...) 0
#define PP_WHILE_CHECK_PP_WHILE_3(...) 0
#define PP_WHILE_CHECK_PP_WHILE_4(...) 0
// ...
#define PP_WHILE_CHECK_PP_WHILE_8(...) 0

PP_AUTO_WHILE       // -> PP_WHILE_1

#define SUM3(max_num, origin_num) \
    PP_IDENTITY(SUM_IMPL PP_AUTO_WHILE(SUM_PRED, SUM_OP, (max_num, origin_num)))

#define SUM_OP4(xy_tuple) SUM_OP_OP_IMPL4 xy_tuple
#define SUM_OP_OP_IMPL4(x, y) (PP_DEC(x), y + SUM3(x, 0))

#define SUM4(max_num, origin_num) \
    PP_IDENTITY(SUM_IMPL PP_AUTO_WHILE(SUM_PRED, SUM_OP4, (max_num, origin_num)))

SUM4(2, a)          // -> a + 0 + 2 + 1 + 0 + 1

PP_AUTO_WHILE هو إصدار التوصيل التلقائي لـ PP_WHILE، والذي يعتمد على التوصيل الذاتي للوظيفة. الرئيسية لهذه الميزة هي PP_AUTO_REC(PP_WHILE_PRED)، حيث يمكن لهذا الميزة العثور على الرقم N للإصدار الحالي المتاح من PP_WHILE_N.

مبدأ الاستنتاج بسيط جدًا، حيث يتم البحث في جميع الإصدارات للعثور على الإصدار الذي يمكن توسيعه بشكل صحيح، ثم يتم إرجاع رقم هذا الإصدار. ومن أجل تعزيز سرعة البحث، يتم استخدام عادة البحث الثنائي. هذا بالضبط ما يقوم به PP_AUTO_REC. يقبل PP_AUTO_REC معلمة واحدة تسمى check، وظيفة check هي التحقق من صلاحية الإصدار. تُعطى هنا دعمًا لنطاق البحث في الإصدارات [1, 4]. يقوم PP_AUTO_REC أولاً بفحص check(2)، إذا كانت القيمة صحيحة، يتم استدعاء PP_AUTO_REC_12 للبحث في النطاق [1, 2]، وإلا يتم استخدام PP_AUTO_REC_34 للبحث في [3, 4]. PP_AUTO_REC_12 يفحص check(1) أولاً، إذا كانت القيمة صحيحة، يُعتبر الإصدار 1 صالحًا، وإلا يتم استخدام الإصدار 2. PP_AUTO_REC_34 يتصرف بنفس الطريقة.

check كيف يمكن كتابة الهام حتى نعرف ما إذا كانت الإصدارات متاحة؟ هنا، سيتم فرض توسيع PP_WHILE_PRED إلى جزئين لالتصاقهما، دعونا نلقي نظرة على الجزء الخلفي PP_WHILE_ ## n(PP_WHILE_FALSE, PP_WHILE_FALSE, PP_WHILE_FALSE): إذا كان PP_WHILE_ ## n متاحًا، فإن هذا الجزء سيتم توسيعه للحصول على قيمة المعلمة val، وهي PP_WHILE_FALSE؛ في حال عدم توفر هذا الجزء، فإن الهام سيبقى ثابتًا، وسيظل PP_WHILE_n(PP_WHILE_FALSE, PP_WHILE_FALSE, PP_WHILE_FALSE).

قم بدمج نتيجة الجزء الخلفي مع الجزء الأمامي PP_WHILE_CHECK_ للحصول على نتيجتين: PP_WHILE_CHECK_PP_WHILE_FALSE أو PP_WHILE_CHECK_PP_WHILE_n(PP_WHILE_FALSE, PP_WHILE_FALSE, PP_WHILE_FALSE)، بحيث يجب أن تُرجع PP_WHILE_CHECK_PP_WHILE_FALSE قيمة 1 كإشارة عن الاستخدام الممكن، وتُرجع PP_WHILE_CHECK_PP_WHILE_n قيمة 0 كدلالة على عدم الاستخدام. وبهذا، نكتمل مع وظيفة تفسير الاستدعاء التلقائي.

المقارنة الحسابية

لا يتساوى:

#define PP_NOT_EQUAL(x, y) PP_NOT_EQUAL_IMPL(x, y)
#define PP_NOT_EQUAL_IMPL(x, y) \
    PP_CONCAT(PP_NOT_EQUAL_CHECK_, PP_NOT_EQUAL_ ## x(0, PP_NOT_EQUAL_ ## y))

#define PP_NOT_EQUAL_CHECK_PP_EQUAL_NIL 1
#define PP_NOT_EQUAL_CHECK_PP_NOT_EQUAL_0(...) 0
#define PP_NOT_EQUAL_CHECK_PP_NOT_EQUAL_1(...) 0
#define PP_NOT_EQUAL_CHECK_PP_NOT_EQUAL_2(...) 0
#define PP_NOT_EQUAL_CHECK_PP_NOT_EQUAL_3(...) 0
#define PP_NOT_EQUAL_CHECK_PP_NOT_EQUAL_4(...) 0
// ...
#define PP_NOT_EQUAL_CHECK_PP_NOT_EQUAL_8(...) 0

#define PP_NOT_EQUAL_0(cond, y) PP_IF(cond, PP_EQUAL_NIL, y(1, PP_EQUAL_NIL))
#define PP_NOT_EQUAL_1(cond, y) PP_IF(cond, PP_EQUAL_NIL, y(1, PP_EQUAL_NIL))
#define PP_NOT_EQUAL_2(cond, y) PP_IF(cond, PP_EQUAL_NIL, y(1, PP_EQUAL_NIL))
#define PP_NOT_EQUAL_3(cond, y) PP_IF(cond, PP_EQUAL_NIL, y(1, PP_EQUAL_NIL))
#define PP_NOT_EQUAL_4(cond, y) PP_IF(cond, PP_EQUAL_NIL, y(1, PP_EQUAL_NIL))
// ...
#define PP_NOT_EQUAL_8(cond, y) PP_IF(cond, PP_EQUAL_NIL, y(1, PP_EQUAL_NIL))

PP_NOT_EQUAL(1, 1)          // -> 0
PP_NOT_EQUAL(3, 1)          // -> 1

عند التحقق من مساواة القيم، يتم استخدام خاصية منع إعادة التشعب المتقدمة، حيث يتم دمج x و y بشكل متكرر لإنشاء الماكرو PP_NOT_EQUAL_x PP_NOT_EQUAL_y، إذا كان x == y، سيتم تجاهل توسيع الماكرو PP_NOT_EQUAL_y، وسيتم دمجه مع PP_NOT_EQUAL_CHECK_ ليتم إرجاع 0 في النهاية. بينما في الحالة المعاكسة، سيقوم النمطان بالتوسع حتى الحصول على PP_EQUAL_NIL، ومن ثم يتم دمجها لتعود بقيمة PP_NOT_EQUAL_CHECK_PP_EQUAL_NIL التي ترجع 1.

متطابق:

#define PP_EQUAL(x, y) PP_NOT(PP_NOT_EQUAL(x, y))

PP_EQUAL(1, 1)              // -> 1
PP_EQUAL(1, 3)              // -> 0

أصغر من أو يساوي:

#define PP_LESS_EQUAL(x, y) PP_NOT(PP_SUB(x, y))

PP_LESS_EQUAL(2, 1)         // -> 0
PP_LESS_EQUAL(1, 1)         // -> 1
PP_LESS_EQUAL(1, 2)         // -> 1

أقل من:

#define PP_LESS(x, y) PP_AND(PP_LESS_EQUAL(x, y), PP_NOT_EQUAL(x, y))

PP_LESS(2, 1)               // -> 0
PP_LESS(1, 2)               // -> 1
PP_LESS(2, 2)               // -> 0

بالإضافة إلى ذلك، هناك المزيد من عمليات المقارنة الحسابية مثل "أكبر من"، "أكبر من أو يساوي" وغيرها، ولن ندخل في تفاصيلها هنا.

العمليات الحسابية

باستخدام PP_AUTO_WHILE، يمكننا تنفيذ العمليات الحسابية الأساسية ودعم العمليات المتداخلة.

الجمع:

#define PP_ADD(x, y) \
    PP_IDENTITY(PP_ADD_IMPL PP_AUTO_WHILE(PP_ADD_PRED, PP_ADD_OP, (x, y)))
#define PP_ADD_IMPL(x, y) x

#define PP_ADD_PRED(xy_tuple) PP_ADD_PRED_IMPL xy_tuple
#define PP_ADD_PRED_IMPL(x, y) y

#define PP_ADD_OP(xy_tuple) PP_ADD_OP_IMPL xy_tuple
#define PP_ADD_OP_IMPL(x, y) (PP_INC(x), PP_DEC(y))

PP_ADD(1, 2)                  // -> 3
PP_ADD(1, PP_ADD(1, 2))       // -> 4

نقصان:

#define PP_SUB(x, y) \
    PP_IDENTITY(PP_SUB_IMPL PP_AUTO_WHILE(PP_SUB_PRED, PP_SUB_OP, (x, y)))
#define PP_SUB_IMPL(x, y) x

#define PP_SUB_PRED(xy_tuple) PP_SUB_PRED_IMPL xy_tuple
#define PP_SUB_PRED_IMPL(x, y) y

#define PP_SUB_OP(xy_tuple) PP_SUB_OP_IMPL xy_tuple
#define PP_SUB_OP_IMPL(x, y) (PP_DEC(x), PP_DEC(y))

PP_SUB(2, 1)                // -> 1
PP_SUB(3, PP_ADD(2, 1))     // -> 0

المضاعفة:

#define PP_MUL(x, y) \
    IDENTITY(PP_MUL_IMPL PP_AUTO_WHILE(PP_MUL_PRED, PP_MUL_OP, (0, x, y)))
#define PP_MUL_IMPL(ret, x, y) ret

#define PP_MUL_PRED(rxy_tuple) PP_MUL_PRED_IMPL rxy_tuple
#define PP_MUL_PRED_IMPL(ret, x, y) y

#define PP_MUL_OP(rxy_tuple) PP_MUL_OP_IMPL rxy_tuple
#define PP_MUL_OP_IMPL(ret, x, y) (PP_ADD(ret, x), x, PP_DEC(y))

PP_MUL(1, 1)                // -> 1
PP_MUL(2, PP_ADD(0, 1))     // -> 2

تم إضافة معامل 'ret' في عملية الضرب هنا، حيث يكون القيمة الابتدائية لها هي '0'، وفي كل تكرار يتم تنفيذ 'ret = ret + x'،

قسمة:

#define PP_DIV(x, y) \
    IDENTITY(PP_DIV_IMPL PP_AUTO_WHILE(PP_DIV_PRED, PP_DIV_OP, (0, x, y)))
#define PP_DIV_IMPL(ret, x, y) ret

#define PP_DIV_PRED(rxy_tuple) PP_DIV_PRED_IMPL rxy_tuple
#define PP_DIV_PRED_IMPL(ret, x, y) PP_LESS_EQUAL(y, x)

#define PP_DIV_OP(rxy_tuple) PP_DIV_OP_IMPL rxy_tuple
#define PP_DIV_OP_IMPL(ret, x, y) (PP_INC(ret), PP_SUB(x, y), y)

PP_DIV(1, 2)                // -> 0
PP_DIV(2, 1)                // -> 2
PP_DIV(2, PP_ADD(1, 1))     // -> 1

يستخدم القسمة العملة PP_LESS_EQUAL ، ويستمر الحلقة فقط في الحالة y <= x.

هياكل البيانات

يمكن أيضًا أن يحتوي هيكل بيانات ، في الحقيقة ، استخدمنا قليلاً هيكل بيانات tuple في الأمام ،PP_REMOVE_PARENS يمكنه إزالة القوسين الخارجيين لـtuple ، وإرجاع العناصر الداخلية. سنستخدم tuple هنا كمثال لمناقشة التنفيذ ذات الصلة ، يمكنك الاطلاع على تنفيذات بنية البيانات الأخرى مثل list، array، على سبيل المثال في Boost.

"tuple" يُعرف كمجموعة عناصر منفصلة مفصولة بفواصل ومحاطة بأقواس: (a، b، c).

#define PP_TUPLE_REMOVE_PARENS(tuple) PP_REMOVE_PARENS(tuple)

احصل على العنصر في الفهرس المحدد
#define PP_TUPLE_ELEM(i, tuple) PP_ARGS_ELEM(i, PP_TUPLE_REMOVE_PARENS(tuple))

"ابتلاع tuple بأكمله وإرجاع فارغ"
#define PP_TUPLE_EAT() PP_EMPTY_EAT

الحصول على الحجم
#define PP_TUPLE_SIZE(tuple) PP_ARGS_SIZE(PP_TUPLE_REMOVE_PARENS(tuple))

// إضافة عنصر
#define PP_TUPLE_PUSH_BACK(elem, tuple) \
    PP_TUPLE_PUSH_BACK_IMPL(PP_TUPLE_SIZE(tuple), elem, tuple)
#define PP_TUPLE_PUSH_BACK_IMPL(size, elem, tuple) \
    (PP_TUPLE_REMOVE_PARENS(tuple) PP_IF(size, PP_COMMA, PP_EMPTY)() elem)

أدرج العناصر
#define PP_TUPLE_INSERT(i, elem, tuple) \
    PP_TUPLE_ELEM( \
        3, \
        PP_AUTO_WHILE( \
            PP_TUPLE_INSERT_PRED, \
            PP_TUPLE_INSERT_OP, \
            (0, i, elem, (), tuple) \
        ) \
    )
#define PP_TUPLE_INSERT_PRED(args) PP_TUPLE_INSERT_PERD_IMPL args
#define PP_TUPLE_INSERT_PERD_IMPL(curi, i, elem, ret, tuple) \
    PP_NOT_EQUAL(PP_TUPLE_SIZE(ret), PP_INC(PP_TUPLE_SIZE(tuple)))
#define PP_TUPLE_INSERT_OP(args) PP_TUPLE_INSERT_OP_IMPL args
#define PP_TUPLE_INSERT_OP_IMPL(curi, i, elem, ret, tuple) \
    ( \
    PP_IF(PP_NOT_EQUAL(PP_TUPLE_SIZE(ret), i), PP_INC(curi), curi), \
    i, elem, \
    PP_TUPLE_PUSH_BACK(\
        PP_IF( \
            PP_NOT_EQUAL(PP_TUPLE_SIZE(ret), i), \
            PP_TUPLE_ELEM(curi, tuple), elem \
        ), \
        ret \
    ), \
    tuple \
    )

إزالة العنصر الأخير
#define PP_TUPLE_POP_BACK(tuple) \
    PP_TUPLE_ELEM( \
        1, \
        PP_AUTO_WHILE( \
            PP_TUPLE_POP_BACK_PRED, \
            PP_TUPLE_POP_BACK_OP, \
            (0, (), tuple) \
        ) \
    )
#define PP_TUPLE_POP_BACK_PRED(args) PP_TUPLE_POP_BACK_PRED_IMPL args
#define PP_TUPLE_POP_BACK_PRED_IMPL(curi, ret, tuple) \
    PP_IF( \
        PP_TUPLE_SIZE(tuple), \
        PP_NOT_EQUAL(PP_TUPLE_SIZE(ret), PP_DEC(PP_TUPLE_SIZE(tuple))), \
        0 \
    )
#define PP_TUPLE_POP_BACK_OP(args) PP_TUPLE_POP_BACK_OP_IMPL args
#define PP_TUPLE_POP_BACK_OP_IMPL(curi, ret, tuple) \
    (PP_INC(curi), PP_TUPLE_PUSH_BACK(PP_TUPLE_ELEM(curi, tuple), ret), tuple)

// إزالة العنصر
#define PP_TUPLE_REMOVE(i, tuple) \
    PP_TUPLE_ELEM( \
        2, \
        PP_AUTO_WHILE( \
            PP_TUPLE_REMOVE_PRED, \
            PP_TUPLE_REMOVE_OP, \
            (0, i, (), tuple) \
        ) \
    )
#define PP_TUPLE_REMOVE_PRED(args) PP_TUPLE_REMOVE_PRED_IMPL args
#define PP_TUPLE_REMOVE_PRED_IMPL(curi, i, ret, tuple) \
    PP_IF( \
        PP_TUPLE_SIZE(tuple), \
        PP_NOT_EQUAL(PP_TUPLE_SIZE(ret), PP_DEC(PP_TUPLE_SIZE(tuple))), \
        0 \
    )
#define PP_TUPLE_REMOVE_OP(args) PP_TUPLE_REMOVE_OP_IMPL args
#define PP_TUPLE_REMOVE_OP_IMPL(curi, i, ret, tuple) \
    ( \
    PP_INC(curi), \
    i, \
    PP_IF( \
        PP_NOT_EQUAL(curi, i), \
        PP_TUPLE_PUSH_BACK(PP_TUPLE_ELEM(curi, tuple), ret), \
        ret \
    ), \
    tuple \
    )

PP_TUPLE_SIZE(())               // -> 0

PP_TUPLE_PUSH_BACK(2, (1))      // -> (1, 2)
PP_TUPLE_PUSH_BACK(2, ())       // -> (2)

PP_TUPLE_INSERT(1, 2, (1, 3))   // -> (1, 2, 3)

PP_TUPLE_POP_BACK(())           // -> ()
PP_TUPLE_POP_BACK((1))          // -> ()
PP_TUPLE_POP_BACK((1, 2, 3))    // -> (1, 2)

PP_TUPLE_REMOVE(1, (1, 2, 3))   // -> (1, 3)
PP_TUPLE_REMOVE(0, (1, 2, 3))   // -> (2, 3)

هنا سنوضح قليلا تنفيذ إدراج العناصر، وكذلك إجراءات الحذف الأخرى تعتمد على نفس المبدأ. يمكنك استخدام PP_TUPLE_INSERT(i، elem، tuple) لإدراج العنصر elem في الموضع i داخل tuple. لتنفيذ هذه العملية، يجب نقل العناصر التي تأتي قبل الموضع i باستخدام PP_TUPLE_PUSH_BACK إلى tuple جديدة (ret)، ثم وضع العنصر elem في الموضع i. بعد ذلك، يتم نقل العناصر الأصلية في tuple والتي تأتي بعد الموضع i إلى ret، وبذلك تصبح ret هي النتيجة المرجوة.

ملخص

الهدف من هذا النص هو توضيح مبادئ وتنفيذات برمجة الماكرو في C/C++، بجانب تسجيل بعض فهمي وتعرفاتي الشخصية، مع أمل في أن يوفر قليلاً من الإرشاد والإلهام للآخرين. يجب ملاحظة أن النص، ورغم طوله البعض، لم يتطرق لبعض تقنيات واستخدامات برمجة الماكرو، مثل الأسلوب المعروف CHAOS_PP المعتمد على طرق الاستدعاء التكرارية المؤجلةقد يكون من المثير للاهتمام التحقق من البيانات ذات الصلة في "BOOST_PP" مثل الـ REPEAT وغيرها.

ترجمة النص إلى اللغة العربية:

عملية تصحيح أخطاء البرمجة الكبيرة هي عملية مؤلمة، يمكننا:

استخدم الخيارات -P -E لإخراج نتائج المعالجة المسبق؛ استعملت النسخة المعدلة الخاصة بي من clang التي ذكرتها سابقًا لدراسة تفصيلية لعملية الفك. قم بتفكيك الدوال المعقدة وفحص نتائج توسيع الدوال الوسيطة. قم بحجب الملفات الرأسية والـ macros غير المتعلقة. في النهاية، يتعين عليك تصور عملية توسيع الماكرو، وبمجرد أن تصبح ملمًا بهذه العملية، سيزيد كفاءة التصحيح أيضًا.

المقال الحالي يحتوي على تعريفات برمجية قمت بإعادة تنفيذها بناءً على فهمي الخاص للمبدأ الأساسي، حيث أقتبست بعض التعريفات من تنفيذ ومراجع Boost الموجودة في المقالات، إذا كانت هناك أية أخطاء، فأنا أرحب بأي تصحيح في أي وقت، وأنا متاح لمناقشة أية قضايا ذات صلة.

جميع رموز هذا المقال متوفرة هنا: تحميلعرض حي عبر الإنترنتعذرًا، لا أستطيع ترجمة النقاط بمعزل، يرجى تقديم سياق أو كلمات إضافية للمساعدة في تقديم الترجمة المناسبة.

اقتباس

  • Boost.Preprocessor (https://bot-man-jl.github.io/articles/?post=2020/Macro-Programming-Art)

Original: https://wiki.disenone.site/ar

This post is protected by CC BY-NC-SA 4.0 agreement, should be reproduced with attribution.

تم ترجمة هذه المشاركة باستخدام ChatGPT، يرجى تقديم ملاحظاتكأشر على أي نقص.