กลับไปหน้าบทความ

อ่าน 12 นาที

แม่แบบวาริเอดิก

ใน การเขียน โปรแกรม คอมพิวเตอร์ เทมเพลตแบบแปรผัน หรือ เจเนริกแบบแปรผัน คือ เทมเพลต ที่รับพารามิเตอร์ประเภทจำนวนแปรผันได้

แม่แบบวาริเอดิก

ใน การเขียน โปรแกรมคอมพิวเตอร์เทมเพลตแบบแปรผันหรือเจเนริกแบบแปรผันคือเทมเพลตที่รับพารามิเตอร์ประเภทจำนวนแปรผันได้

ภาษา C++ (ตั้งแต่ มาตรฐาน C++11 ) และภาษา Dรองรับเทมเพลตแบบแปรผันได้ (variadic templates) แม้ว่าภาษาอื่นๆ เช่นJavaหรือC#จะรองรับฟังก์ชันแบบแปรผันได้ ที่ปลอดภัยต่อชนิดข้อมูล (type-safe variadic functions ) แต่ก็ไม่รองรับเจเนริกแบบแปรผันได้เหมือนกับ C++ และ D

ซี++

ก่อนที่จะมีการนำเทมเพลตแบบแปรผันมาใช้ใน C++ ฟังก์ชันแบบแปรผันสามารถใช้งานได้โดยใช้ มาโคร va_listแบบแปรผันจากC เท่านั้น ซึ่งขาดความปลอดภัยของประเภท คุณสมบัติเทมเพลตแบบแปรผันของ C++ ได้รับการออกแบบโดย Douglas Gregor และ Jaakko Järvi [ 1 ] [ 2 ]และต่อมาได้รับการกำหนดมาตรฐานใน C++11 ก่อน C++11 เทมเพลต (คลาสและฟังก์ชัน) สามารถรับอาร์กิวเมนต์ได้เพียงจำนวนคงที่ ซึ่งต้องระบุเมื่อมีการประกาศเทมเพลตครั้งแรก C++11 อนุญาตให้คำจำกัดความของเทมเพลตรับอาร์กิวเมนต์จำนวนเท่าใดก็ได้ของประเภทใดก็ได้ ดังนั้น วิธีเดียวที่จะได้ฟังก์ชันแบบแปรผันที่มีความปลอดภัยของประเภทคือการใช้เทมเพลตแบบแปรผันกับฟังก์ชัน เนื่องจาก C++ ขาดฟังก์ชันแบบแปรผันที่ไม่ใช้เทมเพลตเช่นเดียวกับใน Java และ C#

// รับอาร์กิวเมนต์ตั้งแต่ศูนย์ตัวขึ้นไปtemplate < typename ... Ts > class Tuple ;

คลาสเทมเพลตข้างต้นTuple(ซึ่งแสดงถึงประเภทผลิตภัณฑ์หรือทูเปิล) จะรับชื่อประเภทจำนวนเท่าใดก็ได้เป็นพารามิเตอร์เทมเพลต ในที่นี้ อินสแตนซ์ของคลาสเทมเพลตข้างต้นถูกสร้างขึ้นโดยมีอาร์กิวเมนต์ประเภทสามตัว:

using String = std :: string ; template < typename T > using Vector = std :: vector <T> ; template < typename K , typename V > using TreeMap = std :: map < K , V > ;Tuple < int , Vector < int > , TreeMap < String , Vector < int >>> someInstanceName ;

จำนวนอาร์กิวเมนต์สามารถเป็นศูนย์ได้ ดังนั้นก็ใช้งานได้เช่นกัน Tuple<>someInstanceName;

หากเทมเพลตแบบแปรผันควรอนุญาตเฉพาะจำนวนอาร์กิวเมนต์ที่เป็นบวกเท่านั้น สามารถใช้คำจำกัดความนี้ได้:

// รับอาร์กิวเมนต์หนึ่งตัวขึ้นไปtemplate < typename First , typename ... Rest > class 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) ใน Java เท่านั้น สามารถใช้แนวคิดstd::same_as[ 3 ]หรือstd::convertible_to(สำหรับประเภทที่อาจตั้งใจให้แปลงได้) [ 4 ]

โดยใช้std :: same_as ;// เทียบเท่ากับการประกาศใน Java // <T> void fn(T... args) template < typename T > void fn ( same_as < T > auto ... args ) { // ... }

นอกจากนี้ยังสามารถจำกัดให้แคบลงไปอีกได้เฉพาะประเภทใดประเภทหนึ่ง:

using std :: convertible_to ; using std :: same_as ; using std :: string ;// เทียบเท่ากับการประกาศใน Java // void foo(int... args) void foo ( same_as < int > auto ... args ) { // ... }// เทียบเท่ากับการประกาศใน Java // 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 ) != '%' ) { // แสร้งทำเป็นแยกวิเคราะห์รูปแบบ: ใช้ได้เฉพาะกับสตริงรูปแบบ 2 ตัวอักษร ( %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 ( someFunction ( args )...); }ขยาย( 42 , "คำตอบ" , จริง);

ซึ่งจะขยายออกไปเป็นสิ่งต่างๆ ดังนี้:

pass ( someFunction ( arg1 ), someFunction ( arg2 ), someFunction ( arg3 ) /* เป็นต้น... */ );

การใช้ฟังก์ชัน "pass" นี้มีความจำเป็น เนื่องจากกระบวนการขยายชุดอาร์กิวเมนต์จะดำเนินการโดยการแยกอาร์กิวเมนต์การเรียกฟังก์ชันด้วยเครื่องหมายจุลภาค ซึ่งไม่เทียบเท่ากับตัวดำเนินการจุลภาคดังนั้น จึงsomeFunction(args)...;ไม่มีทางใช้งานได้ นอกจากนี้ วิธีแก้ปัญหาข้างต้นจะใช้ได้เฉพาะเมื่อประเภทการส่งคืนของsomeFunctionไม่ใช่ เท่านั้นvoidยิ่งไปกว่านั้นsomeFunctionการเรียกจะถูกดำเนินการในลำดับที่ไม่ระบุ เนื่องจากลำดับการประเมินอาร์กิวเมนต์ของฟังก์ชันนั้นไม่แน่นอน เพื่อหลีกเลี่ยงลำดับที่ไม่ระบุ สามารถใช้รายการเริ่มต้นที่อยู่ในวงเล็บปีกกา ซึ่งรับประกันลำดับการประเมินจากซ้ายไปขวาอย่างเคร่งครัด รายการเริ่มต้นต้องมีvoidประเภทการส่งคืนที่ไม่ใช่ แต่สามารถใช้ตัวดำเนินการจุลภาคเพื่อส่งผ่านไป1ยังองค์ประกอบการขยายแต่ละรายการได้

struct Pass { template < typename ... T > explicit Pass ( T ... args ) { // ... } };ส่งผ่าน{( someFunction ( args ), 1 )...};

แทนที่จะเรียกใช้ฟังก์ชัน เราสามารถระบุและเรียกใช้นิพจน์แลมบ์ดาได้โดยตรง ซึ่งช่วยให้สามารถเรียกใช้ลำดับคำสั่งใดๆ ก็ได้โดยตรง

ผ่าน{([ & ]() -> void { std :: println ( "{}" , args ); }(), 1 )...};

อย่างไรก็ตาม ในตัวอย่างนี้ ฟังก์ชันแลมบ์ดาไม่จำเป็น สามารถใช้สูตรธรรมดาแทนได้:

ส่งผ่าน{( std :: println ( "{}" , args ), 1 )...};

ในC++17สามารถเขียนใหม่ได้โดยใช้fold expressionบนตัวดำเนินการคอมมา:

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

อีกวิธีหนึ่งคือการใช้โอเวอร์โหลดกับ "เวอร์ชันสิ้นสุด" ของฟังก์ชัน วิธีนี้ใช้ได้ทั่วไปมากกว่า แต่ต้องใช้โค้ดและความพยายามในการสร้างมากกว่าเล็กน้อย ฟังก์ชันหนึ่งรับอาร์กิวเมนต์หนึ่งตัวที่มีประเภทใดประเภทหนึ่งและชุดอาร์กิวเมนต์ ในขณะที่อีกฟังก์ชันหนึ่งไม่รับทั้งสองอย่าง (หากทั้งสองมีรายการพารามิเตอร์เริ่มต้นเหมือนกัน การเรียกใช้จะกำกวม เนื่องจากชุดพารามิเตอร์แบบแปรผันเพียงอย่างเดียวไม่สามารถแยกแยะการเรียกใช้ได้) ตัวอย่างเช่น:

void func () { // เวอร์ชันการยุติ}template < typename First , typename ... Args > void func ( const First & first , const Args && ... args ) { process ( first ); func ( args ...); // หมายเหตุ: first ไม่ปรากฏในที่นี้! }

หากargs...มีอาร์กิวเมนต์อย่างน้อยหนึ่งรายการ ระบบจะเปลี่ยนเส้นทางไปยังเวอร์ชันที่สอง — ชุดพารามิเตอร์อาจว่างเปล่า ในกรณีนั้น ระบบจะเปลี่ยนเส้นทางไปยังเวอร์ชันการยุติการทำงาน ซึ่งจะไม่ทำอะไรเลย

เทมเพลตแบบแปรผันสามารถใช้ในข้อกำหนดข้อยกเว้น รายการคลาสพื้นฐาน หรือรายการเริ่มต้นของตัวสร้างได้เช่นกัน ตัวอย่างเช่น คลาสสามารถระบุสิ่งต่อไปนี้ได้:

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

ตัวดำเนินการ unpack จะจำลองประเภทสำหรับคลาสพื้นฐานของClassNameโดยที่คลาสนี้จะถูกสืบทอดจากแต่ละประเภทที่ส่งเข้ามา นอกจากนี้ ตัวสร้างจะต้องรับการอ้างอิงไปยังแต่ละคลาสพื้นฐาน เพื่อเริ่มต้นคลาสพื้นฐานClassNameของ

ในส่วนของเทมเพลตฟังก์ชัน พารามิเตอร์แบบแปรผันสามารถส่งต่อได้ เมื่อรวมกับการอ้างอิงแบบสากล (ดูด้านบน) จะทำให้สามารถส่งต่อได้อย่างสมบูรณ์แบบ:

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)ไวยากรณ์จะส่งต่ออาร์กิวเมนต์ในรูปแบบที่ถูกต้องอย่างสมบูรณ์แบบ แม้กระทั่งในแง่ของค่า rvalue ไปยังตัวสร้าง ตัวดำเนินการแยกอาร์กิวเมนต์จะส่งต่อไวยากรณ์การส่งต่อไปยังพารามิเตอร์แต่ละตัว ฟังก์ชันโรงงานนี้จะห่อหุ้มหน่วยความจำที่จัดสรรไว้โดยอัตโนมัติเพื่อstd::shared_ptrความปลอดภัยในระดับหนึ่งเกี่ยวกับปัญหาหน่วยความจำรั่วไหล

นอกจากนี้ จำนวนอาร์กิวเมนต์ในชุดพารามิเตอร์เทมเพลตสามารถกำหนดได้ดังนี้:

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

นิพจน์นี้SizeCarrier<T, U>::SIZEจะให้ผลลัพธ์เป็น 2 ในขณะที่ นิพจน์อื่น SizeCarrier<>::SIZEจะให้ผลลัพธ์เป็น 0

เพื่อนที่หลากหลาย

ในC++26 ได้มีการเพิ่ม variadic friendsเข้ามาในภาษา ซึ่งอาศัย variadic templates เป็นพื้นฐาน

import std ;template < typename ... Friends > class ClassWithFriends { private : int secretValue ;เพื่อนเพื่อน...; สาธารณะ: ระบุClassWithFriends ( int secret ) : secretValue { secret } {} };class A { public : void readSecret ( const class ClassWithFriends < A , B >& instance ) const { std :: println ( "ตรวจสอบค่าความลับจาก A: {}" , instance . secretValue ); } };class B { public : void readSecret ( const class ClassWithFriends < A , B >& instance ) const { std :: println ( "ตรวจสอบค่าความลับจาก B: {}" , instance . secretValue ); } };int main ( int argc , char * argv []) { ClassWithFriends < A , B > secretHolder ( 135 ); A a ; B b ; a . readSecret ( secretHolder ); b . readSecret ( secretHolder ); }

การจัดทำดัชนีแพ็ค

การจัดทำดัชนีแพ็คได้รับการแนะนำใน C++ ใน C++26 ซึ่งดึงประเภทหรือค่าที่ดัชนีที่ระบุของแพ็คด้วยไวยากรณ์แบบเรียกซ้ำน้อยกว่า ในขณะที่ดัชนีจะต้องเป็นนิพจน์คงที่[ 5 ]ไวยากรณ์ของการจัดทำดัชนีแพ็คคือ. id-expression...[expression]

import std ;using std :: index_sequence ; using std :: index_sequence_for ; using 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 ]template for (init_stmtopt; for-range-declaration : expansion-initializer) compound-stmtstd::tuple<Ts...>std::array<T, N>std::vector<T>Ts... args

import 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 ]

import std ;using std :: index_sequence ; using 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++ นั้นมี variadics โดยเฉพาะ pack expansions [ 7 ]

// รับเวกเตอร์จำนวนเท่าใดก็ได้ที่มีประเภทองค์ประกอบใดๆ ก็ได้ และ// ส่งคืนเวกเตอร์ของทูเปิล โดยที่องค์ประกอบที่ i ของเวกเตอร์คือ// ทูเปิลขององค์ประกอบที่ i ของเวกเตอร์อินพุตfn Zip [ ... each ElementType :! type ]( ... each vector : Vector ( each ElementType )) -> Vector (( ... each ElementType )) { ... var each iter : auto = each vector . Begin (); var result : Vector (( ... each ElementType )); while ( ... and each iter != each vector . End ()) { result . push_back (( ... each iter )); ... each iter ++ ; } return result ; }

ดี

คำนิยาม

นิยามของเทมเพลตแบบแปรผันในภาษา D คล้ายคลึงกับนิยามในภาษา C++:

template VariadicTemplate ( Args ...) { // เนื้อหาตรงนี้... }

ในทำนองเดียวกัน ข้อโต้แย้งใดๆ ก็สามารถนำมาอยู่หน้ารายการข้อโต้แย้งได้:

template VariadicTemplate ( T , string value , alias symbol , Args ...) { // ส่วนเนื้อหาตรงนี้... }

การใช้งานพื้นฐาน

อาร์กิวเมนต์แบบแปรผันมีลักษณะการใช้งานคล้ายกับอาร์เรย์คงที่มาก สามารถวนซ้ำ เข้าถึงด้วยดัชนี มีlengthคุณสมบัติ และสามารถแบ่งส่วนได้การดำเนินการจะถูกตีความในระหว่างการคอมไพล์ซึ่งหมายความว่าตัวถูกดำเนินการไม่สามารถเป็นค่าที่ได้จากเวลาทำงาน (เช่น พารามิเตอร์ของฟังก์ชัน)

สิ่งใดก็ตามที่ทราบได้ในระหว่างการคอมไพล์ สามารถส่งผ่านเป็นอาร์กิวเมนต์แบบแปรผันได้ ทำให้อาร์กิวเมนต์แบบแปรผันคล้ายกับอาร์กิวเมนต์แบบนามแฝงของเทมเพลตแต่มีประสิทธิภาพมากกว่า เนื่องจากยังยอมรับชนิดข้อมูลพื้นฐาน (char, short, int...) ด้วย

นี่คือตัวอย่างที่พิมพ์สตริงแทนค่าพารามิเตอร์แบบแปรผันStringOfและStringOf2ให้ผลลัพธ์ที่เหมือนกัน

static int staticInt ;โครงสร้างดัมมี่{}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

เทมเพลตแบบแปรผันมักใช้เพื่อสร้างลำดับของชื่อแทนที่เรียกว่าAliasSeqนิยามของ AliasSeq นั้นค่อนข้างตรงไปตรงมา:

นามแฝงAliasSeq ( Args ...) = Args ;

โครงสร้างนี้ช่วยให้สามารถจัดการรายการอาร์กิวเมนต์แบบแปรผันได้ ซึ่งจะขยายโดยอัตโนมัติ อาร์กิวเมนต์ต้องเป็นสัญลักษณ์หรือค่าที่ทราบได้ในระหว่างการคอมไพล์ ซึ่งรวมถึงค่า ประเภท ฟังก์ชัน หรือแม้แต่เทมเพลตที่ไม่ใช่แบบพิเศษ โครงสร้างนี้ช่วยให้สามารถดำเนินการใดๆ ก็ได้ตามที่คุณคาดหวัง:

import std.meta ;void main () { // หมายเหตุ: AliasSeq ไม่สามารถแก้ไขได้ และไม่สามารถผูกค่าใหม่ให้กับ alias ได้ ดังนั้นเราจะต้องกำหนดชื่อใหม่สำหรับการแก้ไขของเรา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เราสามารถจำลองพารามิเตอร์แบบแปรผันได้โดยใช้ชนิดข้อมูลหลักjava.lang.Object(เช่นเดียวกับใน C#/.NET โดยใช้ `variadic` System.Object) อย่างไรก็ตาม วิธีนี้ขาดความปลอดภัยของชนิดข้อมูลแบบเดียวกับพารามิเตอร์แบบแปรผันในสไตล์ C++

import 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 ]

ดูเพิ่มเติม

สำหรับบทความเกี่ยวกับโครงสร้างแบบแปรผันอื่นๆ นอกเหนือจากเทมเพลต

  • ร่างฉบับทำงานสำหรับภาษา C++, 16 มกราคม 2555
  • เทมเพลตแบบแปรผันในภาษา D
ดึงข้อมูลมาจาก " https://en.wikipedia.org/w/index.php?title=Variadic_template&oldid=1354690762 "

สรุปเนื้อหา

ข้อมูลสำคัญจากบทความ

ข้อมูลสำคัญเกี่ยวกับ แม่แบบวาริเอดิก

ใน การเขียน โปรแกรม คอมพิวเตอร์ เทมเพลตแบบแปรผัน หรือ เจเนริกแบบแปรผัน คือ เทมเพลต ที่รับพารามิเตอร์ประเภทจำนวนแปรผันได้

ซี++

ก่อนที่จะมีการนำเทมเพลตแบบแปรผันมาใช้ใน C++ ฟังก์ชันแบบแปรผันสามารถใช้งานได้โดยใช้ มาโคร va_list แบบ แปรผัน จาก C เท่านั้น ซึ่งขาดความปลอดภัยของประเภท คุณสมบัติเทมเพลตแบบแปรผันของ C++ ได้รับการออกแบบโดย Douglas Gregor และ Jaakko Järvi [ 1 ] [ 2 ]...

เพื่อนที่หลากหลาย

ใน C++26 ได้มีการเพิ่ม variadic friends เข้ามาในภาษา ซึ่งอาศัย variadic templates เป็นพื้นฐาน

การจัดทำดัชนีแพ็ค

การจัดทำดัชนีแพ็คได้รับการแนะนำใน C++ ใน C++26 ซึ่งดึงประเภทหรือค่าที่ดัชนีที่ระบุของแพ็คด้วยไวยากรณ์แบบเรียกซ้ำน้อยกว่า ในขณะที่ดัชนีจะต้องเป็นนิพจน์คงที่ [ 5 ] ไวยากรณ์ของการจัดทำดัชนีแพ็คคือ. id-expression ...[ expression ]