ما وراء كلمتي "typename" و "class"؟ الفروق التي يجب أن تعرفها
\n\nإذا كنت قد كتبت قوالب بلغة C++ من قبل، فبالتأكيد واجهت كلمتين مفتاحيتين تبدوان قابلتين للتبادل: typename و class. قد يبدو الأمر بسيطًا، لكن هذا السؤال حصد آلاف المشاهدات على Stack Overflow. لماذا؟ لأن وراء البساطة الظاهرة يكمن مأزق قد يعطل ترجمة الكود الخاص بك.
في هذه المقالة، سنحسم الأمر مرة وإلى الأبد: ما الفرق بين typename و class في قوالب C++، ومتى يمكن استخدامهما كمرادفين، ومتى يؤدي ذلك إلى خطأ. لا حشو - فقط كود وأمثلة وقواعد واضحة.
\n\nإجابة مختصرة: هل هناك فرق أصلاً؟
\n\nالإجابة الأكثر شيوعًا على Stack Overflow هي: لإعلان معاملات القالب - لا فرق. يمكنك كتابة:
\n\ntemplate <typename T> void foo(T t);\nأو
\ntemplate <class T> void foo(T t);\n\nكلا الخيارين قانونيان تمامًا ويقومان بنفس الشيء: إعلان معامل القالب T. تاريخيًا، ظهر class أولاً (في C++98)، بينما أُضيف typename لاحقًا للتأكيد على أن المعامل يمكن أن يكون ليس فقط class بل أي نوع (int, double, مؤشر، إلخ).
لكن الشيطان يكمن في التفاصيل. هناك سيناريو واحد يكون فيه typename إلزاميًا، بينما class لن يعمل. وهذا هو الأسماء التابعة (dependent names).
المأزق الرئيسي: الأنواع التابعة (dependent types)
\n\nتخيل موقفًا: تكتب قالبًا، وتحتاج داخله إلى الوصول إلى نوع مضمن في معامل القالب. على سبيل المثال، لديك حاوية وتريد الحصول على مكررها:
\n\ntemplate <typename Container>\nvoid printFirst(const Container& c) {\n Container::const_iterator it = c.begin(); // خطأ!\n std::cout << *it;\n}\n\nلماذا لا يتم ترجمة هذا؟ لأن Container::const_iterator هو اسم تابع. المترجم في مرحلة التحليل لا يعرف ما هو Container. قد يكون class، أو قد يكون اسم متغير ثابت؟ افتراضيًا، تعتبر C++ أن Container::const_iterator هو اسم متغير (أو عضو بيانات ثابت)، وليس نوعًا. والكلمة المفتاحية typename فقط هي التي تخبر المترجم: "هذا نوع!".
نصحح:
\n\ntemplate <typename Container>\nvoid printFirst(const Container& c) {\n typename Container::const_iterator it = c.begin(); // صحيح!\n std::cout << *it;\n}\n\nهام: هنا class لن يصلح. كتابة class Container::const_iterator هي خطأ نحوي. typename هو الخيار الصحيح الوحيد.
متى تستخدم typename بدلاً من class؟
\n\nتذكر قاعدة بسيطة: يُستخدم typename في حالتين:
- \n
- كمعامل قالب (مرادف لـ
class):template <typename T>\n - للإشارة إلى أن الاسم التابع هو نوع:
typename T::value_type\n
أما class فيُستخدم فقط للحالة الأولى. وحتى ذلك الحين، يفضل العديد من مبرمجي C++ استخدام typename للمعاملات لتجنب الخلط مع إعلان class.
مثال كامل مع الشروحات
\n\nدعنا نحلل كودًا حقيقيًا يوضح الفرق بين typename و class في قوالب C++ عمليًا:
\n\n#include <iostream>\n#include <vector>\n#include <list>\n\n// الخطوة 1: قالب مع class (أو typename - لا فرق)\ntemplate <class T>\nclass MyContainer {\npublic:\n // الخطوة 2: نستخدم typename للنوع التابع\n // T::value_type هو نوع، وليس