قالب متغير

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

تدعم لغة C++ (منذ معيار C++11 ) ولغة البرمجة D القوالب ذات الوسائط المتغيرة . بينما توفر لغات أخرى، مثل Java أو C#، دعمًا للدوال ذات الوسائط المتغيرة الآمنة من حيث النوع ، إلا أنها لا توفر دعمًا للأنواع العامة ذات الوسائط المتغيرة كما هو الحال في C++ وD.

لغة سي++

قبل إدخال القوالب المتغيرة الوسائط إلى لغة C++، كان بالإمكان تنفيذ الدوال المتغيرة الوسائط فقط باستخدام va_listوحدات الماكرو المتغيرة الوسائط من لغة C ، والتي تفتقر إلى أمان النوع . صُممت ميزة القوالب المتغيرة الوسائط في C++ بواسطة دوغلاس غريغور وجاكو يارفي [ 1 ] [ 2 ] ، وتم توحيدها لاحقًا في C++11. قبل C++11، كانت القوالب (الفئات والدوال) تقبل عددًا ثابتًا من الوسائط، والذي كان يجب تحديده عند تعريف القالب لأول مرة. يسمح C++11 بتعريف القوالب بقبول أي عدد من الوسائط من أي نوع. وبالتالي، فإن الطريقة الوحيدة للحصول على دوال متغيرة الوسائط آمنة النوع هي من خلال القوالب المتغيرة الوسائط، حيث تفتقر C++ إلى دوال متغيرة الوسائط غير المعتمدة على القوالب مثل تلك الموجودة في Java وC#.

// يأخذ صفرًا أو أكثر من الوسائط template < typename ... Ts > class Tuple ;

يأخذ قالب الفئة المذكور أعلاه Tuple<Ts...>(الذي يمثل نوع منتج أو مجموعة) أي عدد من أسماء الأنواع كمعاملات للقالب . هنا، يتم إنشاء نسخة من قالب الفئة المذكور أعلاه بثلاثة وسائط من النوع:

باستخدام String = std :: string ؛ قالب < typename T > باستخدام Vector = std :: vector <T> ؛ قالب < typename K , typename V > باستخدام TreeMap = std :: map < K , V > ؛Tuple < int , Vector <int> , TreeMap < String , Vector <int> >> t ;

يمكن أن يكون عدد الوسائط صفرًا، لذا سيعمل أيضًا.Tuple<>t;

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

// يأخذ وسيطًا واحدًا أو أكثر template < typename First , typename ... Rest > class Tuple ;

يستخدم أحد التطبيقات الشائعة للزوج المرتب (tuple) التفكيك المتكرر على النحو التالي:

// قالب التصريح المسبق < typename ... Ts > class Tuple ;// قالب الحالة الفارغة <> فئة Tuple <> {};// قالب الحالة التكرارية < typename Head , typename ... Tail > class Tuple < Head , Tail ... > { private : Head head ; Tuple < Tail ... > tail ; public : Tuple () = default ; explicit Tuple ( const Head & head , const Tail & ... tail ) : head { head }, tail { tail ...} {}// ... };

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

باستخدام std :: string_view ؛template < typename ... Args > void myPrintf ( string_view fmt , Args ... parameters );

يؤدي عامل الحذف ( ... ) وظيفتين. فعندما يظهر على يسار اسم مُعامل، يُعلن عن حزمة مُعاملات. وباستخدام حزمة المُعاملات، يُمكن للمستخدم ربط صفر أو أكثر من الوسائط بمعاملات القوالب المتغيرة. كما يُمكن استخدام حزم المُعاملات للمعاملات غير النوعية. في المقابل، عندما يظهر عامل الحذف على يمين وسيط قالب أو وسيط استدعاء دالة ، فإنه يفك حزم المُعاملات إلى وسائط منفصلة، ​​كما هو موضح في متن المثال أدناه. عمليًا، يؤدي استخدام عامل الحذف في الكود إلى تكرار التعبير الكامل الذي يسبق الحذف لكل وسيط لاحق يتم فكه من حزمة الوسائط، مع فصل التعبيرات بفواصل.args...printf()

غالبًا ما يتم تنفيذ مُركِّب النقطة الثابتة باستخدام وسائط القالب المتغيرة (لاحظ أن هذا يُعادل ) :[](auto&&...args)[]<template...Args>(Args&&...args)

auto fix = []( auto f ) { return [ f ]( auto && ... args ) -> decltype ( auto ) { return f ( f , std :: forward < decltype ( args ) > ( args )...); }; };auto factorial = fix ([]( auto self , long n ) -> long { return n == 0 ? 1 : n * self ( self , n - 1 ); });std :: println ( "5! = {}" , factorial ( 5 )); // يطبع 120

لتقييد المعامل ليكون من نوع معين فقط T(على غرار T... args) في جافا، يمكن استخدام المفهوم [ std::same_as<T, U>3 ] أو ( std::convertible_to<From, To>للأنواع التي قد يُراد تحويلها). [ 4 ]

باستخدام std :: same_as ;// مكافئ لتصريح جافا // <T> void fn(T... args) template < typename T > void fn ( same_as < T > auto ... args ) { // ... }

ويمكن حصر ذلك بشكل أكبر في أي نوع محدد:

باستخدام std :: convertible_to ؛ باستخدام std :: same_as ؛ باستخدام std :: string ؛// مكافئ لتصريح جافا // void foo(int... args) void foo ( same_as < int > auto ... args ) { // ... }// مكافئ لتصريح جافا // void bar(String... args) void bar ( convertible_to < string > auto ... args ) { // ... }

غالبًا ما يكون استخدام القوالب ذات المعاملات المتغيرة متكررًا. ولا تكون المعاملات المتغيرة نفسها متاحة بسهولة لتنفيذ دالة أو فئة. لذلك، فإن الآلية النموذجية لتعريف printf()بديل للمعاملات المتغيرة في C++11 ستكون كما يلي:

باستخدام std :: runtime_error ؛// الحالة الأساسية void myPrintf ( const char s []) { while ( * s ) { if ( * s == '%' ) { if ( * ( s + 1 ) == '%' ) { ++ s ; } else { throw runtime_error ( "سلسلة تنسيق غير صالحة: وسائط مفقودة" ); } } std :: println ( "{}" , * s ++ ); } }// قالب تكراري < typename T , typename ... Args > void myPrintf ( const char s [], T value , Args ... args ) { while ( * s ) { if ( * s == '%' ) { if ( * ( s + 1 ) != '%' ) { // التظاهر بتحليل التنسيق: يعمل فقط على سلاسل التنسيق المكونة من حرفين ( %d, %f, إلخ ); يفشل مع %5.4f s += 2 ; // طباعة القيمة std :: println ( "{}" , value ); // يتم استدعاؤه حتى عندما يكون *s يساوي 0 ولكنه لا يفعل شيئًا في هذه الحالة (ويتجاهل الوسائط الإضافية) myPrintf ( s , args ...); return ; } ++ s ; } std :: println ( "{}" , * s ++ ); } }

هذا قالب تكراري. لاحظ أن نسخة القالب المتغيرة myPrintf()تستدعي نفسها، أو (في حالة كونها args...فارغة) تستدعي الحالة الأساسية.

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

template < typename ... Args > inline void pass ( Args && ... args ) { // ... }

والتي يمكن استخدامها على النحو التالي:

template < typename ... Args > inline void expand ( Args && ... args ) { pass ( foo ( args )...); }توسيع ( 42 ، "إجابة" ، صحيح );

والذي سيتوسع إلى شيء مثل:

pass ( foo ( arg1 ), foo ( arg2 ), foo ( arg3 ) /* إلخ... */ );

يُعدّ استخدام دالة "pass" ضروريًا، لأنّ توسيع حزمة الوسائط يتمّ بفصل وسائط استدعاء الدالة بفواصل، وهي ليست مكافئة لمعامل الفاصلة . لذلك، لن تعمل هذه الطريقة أبدًا. علاوة على ذلك، لن يعمل الحلّ المذكور أعلاه إلا إذا لم يكن نوع الإرجاع للدالة `true` . بالإضافة إلى ذلك، سيتمّ تنفيذ الاستدعاءات بترتيب غير مُحدّد، لأنّ ترتيب تقييم وسائط الدالة غير مُعرّف. لتجنّب هذا الترتيب غير المُحدّد، يُمكن استخدام قوائم التهيئة المُحاطة بأقواس معقوفة، والتي تضمن ترتيبًا صارمًا للتقييم من اليسار إلى اليمين. تتطلّب قائمة التهيئة نوع إرجاع غير `true`، ولكن يُمكن استخدام معامل الفاصلة لإرجاع قيمة لكلّ عنصر توسيع.foo(args)...;foo()voidfoo()void1

struct Pass { template < typename ... T > explicit Pass ( T ... args ) { // ... } };مرر {( foo ( args ), 1 )...};

بدلاً من تنفيذ دالة، يمكن تحديد تعبير لامدا وتنفيذه في مكانه، مما يسمح بتنفيذ تسلسلات عشوائية من العبارات في مكانها.

Pass {([ & ]() -> void { std :: println ( "{}" , args ); }(), 1 )...};

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

مرر {( std :: println ( "{}" , args ), 1 )...};

في لغة C++17 ، يمكن إعادة كتابة هذه باستخدام تعبيرات الطي على عامل الفاصلة:

([ & ]() -> void { std :: println ( "{}" , args ); }(), ...); (( std :: println ( "{}" , args )), ...);

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

void foo () { // نسخة الإنهاء }template < typename First , typename ... Args > void foo ( const First & first , const Args && ... args ) { process ( first ); foo ( args ...); // ملاحظة: first غير موجود هنا! }

إذا args...احتوى على وسيط واحد على الأقل، فسيتم إعادة التوجيه إلى الإصدار الثاني - يمكن أن تكون حزمة المعلمات فارغة، وفي هذه الحالة سيتم إعادة التوجيه ببساطة إلى إصدار الإنهاء، والذي لن يفعل شيئًا.

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

template < typename ... Bases > class Foo : public Bases ... { public : explicit Foo ( Bases && ... bases ) : Bases ( bases )... {} };

سيقوم عامل فك التغليف بتكرار أنواع الفئات الأساسية لـ Foo، بحيث يتم اشتقاق هذه الفئة من كل نوع من الأنواع التي تم تمريرها. كما يجب أن يأخذ المُنشئ مرجعًا لكل فئة أساسية، وذلك لتهيئة الفئات الأساسية لـ Foo.

فيما يتعلق بقوالب الدوال، يمكن تمرير المعاملات المتغيرة. وعند دمجها مع المراجع العامة (انظر أعلاه)، يتيح ذلك تمريرًا مثاليًا، كما في مثال المؤشر الذكي هذا :

template < typename T > using SharedPtr = std :: shared_ptr < T > ;template < typename T > struct SharedPtrAllocator { template < typename ... Args > SharedPtr < T > construct ( Args && ... params ) { return SharedPtr < T > ( new T ( std :: forward < Args > ( params )...)); } };

يقوم هذا بفك قائمة الوسائط إلى مُنشئ الكائن T. يُمرر بناء الجملة الوسائط بشكل صحيح، حتى فيما يتعلق بكونها قيمًا يمينية، إلى المُنشئ. سينقل عامل فك التعبئة بناء جملة التمرير إلى كل مُعامل. تقوم دالة المصنع هذه بتغليف الذاكرة المُخصصة تلقائيًا في غلاف برمجي لتوفير درجة من الأمان فيما يتعلق بتسرب الذاكرة.std::forward<Args...>(params)std::shared_ptr<T>

بالإضافة إلى ذلك، يمكن تحديد عدد الوسائط في حزمة معلمات القالب على النحو التالي:

template < typename ... Args > struct SizeCarrier { static constexpr size_t SIZE = sizeof ...( Args ); };

سيعطي التعبير قيمة ، بينما سيعطي .SizeCarrier<T,U>::SIZE2SizeCarrier<>::SIZE0

أصدقاء متغيرون

في C++26 ، تمت إضافة الأصدقاء المتغيرين إلى اللغة، والتي تعتمد على القوالب المتغيرة.

استيراد std ؛template < typename ... Friends > class WithFriends { private : int secretValue ;صديق الأصدقاء ...؛ عام : صريح WithFriends ( int secret ) : secretValue { secret } {} };class A { public : void readSecret ( const class WithFriends < A , B >& instance ) const { std :: println ( "فحص القيمة السرية من A: {}" , instance . secretValue ); } };class B { public : void readSecret ( const class WithFriends < A , B >& instance ) const { std :: println ( "فحص القيمة السرية من B: {}" , instance . secretValue ); } };int main ( int argc , char * argv []) { WithFriends < A , B > secretHolder ( 135 ); A a ; B b ; a . readSecret ( secretHolder ); b . readSecret ( secretHolder ); }

فهرسة الحزم

تم تقديم فهرسة الحزم إلى لغة C++ في الإصدار C++26، وهي تسترجع نوعًا أو قيمة عند فهرس محدد في حزمة باستخدام صيغة أقل تكرارًا، مع اشتراط أن يكون الفهرس تعبيرًا ثابتًا. [ 5 ] صيغة فهرسة الحزم هي :id-expression...[expression]

استيراد std ؛باستخدام std :: index_sequence ؛ باستخدام std :: index_sequence_for ؛ باستخدام std :: tuple ؛// تأخذ هذه الدالة، reversed()، أي عدد من الوسائط وتعيد // tuple<Ts...> تحتوي على نفس الوسائط بترتيب معكوس، حيث يتم تقليص كل // T في Ts... إلى decay_t<T>. template < typename ... Ts > requires ( sizeof ...( Ts ) > 0 ) constexpr auto reversed ( Ts && ... args ) noexcept { constexpr size_t MAX_INDEX_SIZE = sizeof ...( args ) - 1 ; auto reverser = [ & args ...] < size_t ... Is > ( index_sequence < Is ... > ) { auto reverseForOneIndex = [ & args ...] < size_t I > { return args ...[ MAX_INDEX_SIZE - I ] ; }; return std :: make_tuple ( reverseForOneIndex . template operator () < Is > ()...); }; return reverser ( index_sequence_for < Ts ... > {}); }int main () { // نوع النتيجة: // tuple<char, long double, double, float, const char*, int> constexpr tuple result = reversed ( 13 , "hello" , 2.718f , 3.14 , 9.9375L , 'X' ); static_assert ( std :: get < 0 > ( result ) == 'X' ); }

التكرار على حزم الأنواع والمعلمات

تضيف لغة C++26 عبارات التوسيع ( الحلقات)، التي يتم تعريفها باستخدام للتكرار عليها ، وأنواع المجموعات مثل و ، وحزم النوع/المعاملات ( ). [ 6 ]templatefortemplate for (init_stmtopt; for-range-declaration : expansion-initializer) compound-stmtstd::tuple<Ts...>std::array<T, N>std::vector<T>Ts...args

استيراد std ؛void printAllObjects ( auto && ... args ) { template for ( auto && arg : { args ...}) { std :: print ( "{}" , arg ); } }int main () { printAllObjects ( "13 + 31 = " , 44 , " \n Hello, world! \n " , 3.14 , '\n' , 2.718f , '\n' , 735.9572L , false ); return 0 ; }

تسمح عبارات التوسيع بعبارات breakالتحكم continueفي التدفق ، والتي تتصرف بنفس طريقة أي حلقة أخرى. [ 6 ]

استيراد std ؛باستخدام std :: index_sequence ؛ باستخدام std :: index_sequence_for ؛template < typename ... Ts > constexpr bool areEvenIndicesEven ( Ts && ... args ) noexcept { auto checkEvenIndices = [ & args ...] < size_t ... Is > ([[ maybe_unused ]] index_sequence < Is ... > s ) -> bool { template for ( constexpr size_t I : { Is ...}) { if constexpr ( I % 2 != 0 ) { continue ; } if ( args ...[ I ] % 2 != 0 ) { return false ; } } return true ; }; return checkEvenIndices ( index_sequence_for < Ts ... > {}); }int main () { static_assert ( areEvenIndicesEven ( 0 , 1 , 2 , 3 )); }

الكربون

توفر لغة Carbon ، المصممة مع مراعاة التوافق مع لغة C++، إمكانية استخدام المتغيرات، وخاصة توسيعات الحزم. [ 7 ]

// تأخذ عددًا غير محدد من المتجهات بأنواع عناصر غير محددة، // وتعيد متجهًا من الأزواج حيث يكون العنصر رقم i من المتجه // زوجًا من العناصر رقم i من متجهات الإدخال. fn Zip [ ... eachElementType :! type ] ( ... eachvector : Vector ( eachElementType ) ) -> Vector (( ... eachElementType ) ) { ... var eachiter : auto = eachvector.Begin ( ); var result : Vector ( ( ... eachElementType ) ) ; while ( ... andeachiter ! = eachvector.End ( ) ) { result.push_back ( ( ... eachiter ) ) ; ... eachiter ++ ; } return result ; }

د

تعريف

إن تعريف القوالب المتغيرة في لغة D مشابه لنظيرتها في لغة C++:

template VariadicTemplate ( Args ...) { // نص القالب هنا... }

وبالمثل، يمكن لأي حجة أن تسبق قائمة الحجج:

template VariadicTemplate ( T , string value , alias symbol , Args ...) { // body here... }

الاستخدام الأساسي

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

يمكن تمرير أي شيء معروف وقت الترجمة كوسيط متغير. وهذا يجعل الوسائط المتغيرة مشابهة لوسائط الأسماء المستعارة للقوالب ، ولكنها أكثر قوة ، لأنها تقبل أيضًا الأنواع الأساسية ( char،، ...).shortint

إليك مثال يطبع التمثيل النصي للمعاملات المتغيرة، StringOfوينتج StringOf2نتائج متساوية.

static int staticInt ;struct Dummy {}void main () { pragma ( msg , StringOf !( "Hello world" , uint , Dummy , 42 , staticInt )); pragma ( msg , StringOf2 !( "Hello world" , uint , Dummy , 42 , staticInt )); }template StringOf ( Args ...) { enum StringOf = Args [ 0 ]. stringof ~ StringOf !( Args [ 1. .$]); }template StringOf () { enum StringOf = "" ; }template StringOf2 ( Args ...) { static if ( Args . length == 0 ) { enum StringOf2 = "" ; } else { enum StringOf2 = Args [ 0 ]. stringof ~ StringOf2 !( Args [ 1. .$]); } }

المخرجات:

"مرحباً بالعالم" uintDummy42staticInt "مرحباً بالعالم" uintDummy42staticInt 

AliasSeq

تُستخدم القوالب المتغيرة غالبًا لإنشاء سلسلة من الأسماء المستعارة، تُسمى std.meta.AliasSeq . تعريفها في الواقع بسيط للغاية:std.meta.AliasSeq

الاسم المستعار AliasSeq ( Args ...) = Args ;

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

استيراد std.meta ؛void main () { // ملاحظة: لا يمكن تعديل AliasSeq، ولا يمكن إعادة ربط الاسم المستعار، لذا سنحتاج إلى تعريف أسماء جديدة لتعديلاتنا. alias numbers = AliasSeq !( 1 , 2 , 3 , 4 , 5 , 6 ); // التقطيع alias lastHalf = numbers [$ / 2 .. $]; static assert ( lastHalf == AliasSeq !( 4 , 5 , 6 )); // التوسيع التلقائي لـ AliasSeq alias digits = AliasSeq !( 0 , numbers , 7 , 8 , 9 ); static assert ( digits == AliasSeq !( 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 )); // توفر std.meta قوالب للعمل مع AliasSeq، مثل anySatisfy و allSatisfy و staticMap و Filter. alias evenNumbers = Filter !( isEven , digits ); static assert ( evenNumbers == AliasSeq !( 0 , 2 , 4 , 6 , 8 )); }template isEven ( int number ) { enum isEven = ( 0 == ( number % 2 )); }

في لغة C# / .NET ، تحتوي الفئة System.Tupleعلى نسخ محددة من 1 إلى 8 عناصر. ولإنشاء مجموعة من تسعة عناصر أو أكثر، يتم توفير المعامل الأخير TRestكمجموعة Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>أخرى، لأن لغة C# تفتقر إلى معلمات القوالب المتغيرة. [ 8 ]

في لغة جافا ، يمكن محاكاة المعاملات المتغيرة باستخدام النوع الأعلى java.lang.Object(وبالمثل في C#/.NET، مع System.Object)، إلا أن هذا يفتقر إلى أمان النوع للمعاملات المتغيرة على نمط C++.

استيراد java.util.List ;void printAllObjects ( Object ... args ) { for ( Object arg : args ) { System . out . println ( arg ); } }void main () { printAllObjects ( "Hello, world!" , 42 , 3.14159f , true , List . of ( 1 , 2 , 3 )); }

توجد مقترحات للأنواع العامة المتغيرة في لغة Rust منذ عام 2013 [ 9 ] ، ولكنها لم تُضَف إلى اللغة حتى الآن. مع ذلك، يمكن محاكاتها بشكل مشابه باستخدام وحدات الماكرو المتغيرة ، والتي يمكن استخدامها لتوليد التعليمات البرمجية. [ 10 ]

انظر أيضاً

للاطلاع على المقالات المتعلقة بالبنى المتغيرة بخلاف القوالب

مراجع

  1. دوغلاس غريغور وجاكو يارفي (فبراير 2008). "قوالب متغيرة لـ C++0x" . مجلة تكنولوجيا الكائنات . 7 (2، عدد خاص بمسار البرمجة الكائنية في مؤتمر SAC 2007): 31-51 . doi : 10.5381/jot.2008.7.2.a2 .
  2. دوغلاس غريغور؛ جاكو يارفي وغاري باول. (فبراير 2004). "قوالب متغيرة الوسائط. رقم N1603=04-0043 في مراسلات لجنة معايير ISO C++ قبل سيدني".
  3. cppreference.com. "std::same_as" . cppreference.com . cppreference.com . تم الاطلاع عليه بتاريخ 17 مايو 2026 .
  4. cppreference.com. "std::convertible_to" . cppreference.com . cppreference.com . تم الاطلاع عليه بتاريخ 17 مايو 2026 .
  5. "فهرسة الحزم (منذ C++26)" . cppreference.com . 16 أبريل 2025. تم الاطلاع عليه في 29 مارس 2026 .
  6. 1 2 دان كاتز (20 يونيو 2025). "بيانات التوسع" . open-std.org . WG21.
  7. مشروع لغة كاربون (1 أبريل 2026). "Variadics - توثيق لغة كاربون" . docs.carbon-lang.dev . مشروع لغة كاربون.
  8. "فئة Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>" . learn.microsoft.com . Microsoft Learn . تم الاطلاع عليه بتاريخ 14 أكتوبر 2025 .
  9. فريق Rust (12 يوليو 2023). "الأنواع العامة المتغيرة" . github.com . فريق Rust.
  10. فريق Rust (1 أبريل 2026). "واجهات متغيرة" . rust-lang.org . Rust بالأمثلة.