(ดูที่นี่สำหรับคำตอบ C ++ 11 ของฉันด้วย )
ในการวิเคราะห์โปรแกรม C ++ คอมไพเลอร์จำเป็นต้องรู้ว่าชื่อบางประเภทเป็นประเภทใดหรือไม่ ตัวอย่างต่อไปนี้แสดงให้เห็นว่า:
t * f;
ควรแยกวิเคราะห์นี้อย่างไร สำหรับหลาย ๆ ภาษาคอมไพเลอร์ไม่จำเป็นต้องทราบความหมายของชื่อเพื่อที่จะวิเคราะห์และรู้ว่าการกระทำของบรรทัดโค้ดคืออะไร ใน C ++ ข้างต้นสามารถให้การตีความที่แตกต่างกันอย่างมากมายโดยขึ้นอยู่กับความt
หมาย f
ถ้าเป็นประเภทแล้วมันจะมีการประกาศตัวชี้ อย่างไรก็ตามถ้ามันไม่ใช่ประเภทมันจะเป็นการคูณ ดังนั้นมาตรฐาน C ++ กล่าวไว้ในย่อหน้า (3/7):
บางชื่อแสดงถึงประเภทหรือเทมเพลต โดยทั่วไปเมื่อใดก็ตามที่พบชื่อมีความจำเป็นต้องพิจารณาว่าชื่อนั้นหมายถึงหนึ่งในเอนทิตี้เหล่านี้หรือไม่ก่อนที่จะแยกวิเคราะห์โปรแกรมที่มีอยู่ กระบวนการที่กำหนดสิ่งนี้เรียกว่าการค้นหาชื่อ
คอมไพเลอร์จะค้นหาว่าชื่อt::x
อ้างอิงถึงอย่างไรหากt
อ้างถึงพารามิเตอร์ประเภทเทมเพลต x
อาจเป็นสมาชิกข้อมูลคงที่ที่สามารถคูณหรืออาจเป็นชั้นซ้อนหรือ typedef ที่สามารถให้ผลการประกาศ หากชื่อมีคุณสมบัตินี้ - จะไม่สามารถค้นหาได้จนกว่าจะรู้ถึงข้อโต้แย้งของแม่แบบจริง - จากนั้นจะเรียกว่าชื่อที่ขึ้นต่อกัน (ขึ้นอยู่กับพารามิเตอร์ของแม่แบบ)
คุณอาจแนะนำให้รอจนกว่าผู้ใช้จะเริ่มต้นเทมเพลต:
รอ Let 's จนกว่าผู้ใช้จะ instantiates t::x * f;
แม่แบบแล้วภายหลังพบความหมายที่แท้จริงของ
สิ่งนี้จะใช้งานได้จริงและได้รับอนุญาตจากมาตรฐานให้เป็นแนวทางการปฏิบัติ คอมไพเลอร์เหล่านี้โดยทั่วไปจะคัดลอกข้อความของเทมเพลตลงในบัฟเฟอร์ภายในและเมื่อจำเป็นต้องมีการสร้างอินสแตนซ์เท่านั้นพวกเขาจะแยกวิเคราะห์เทมเพลตและอาจตรวจพบข้อผิดพลาดในการกำหนด แต่แทนที่จะรบกวนผู้ใช้เทมเพลต (ผู้ร่วมงานที่แย่!) โดยมีข้อผิดพลาดจากผู้สร้างเทมเพลตการใช้งานอื่น ๆ เลือกที่จะตรวจสอบเทมเพลตก่อนและให้ข้อผิดพลาดในการกำหนดโดยเร็วที่สุดก่อนที่จะเกิดอินสแตนซ์
ดังนั้นจะต้องมีวิธีที่จะบอกคอมไพเลอร์ว่าชื่อบางชื่อเป็นประเภทและชื่อบางชื่อไม่ได้
คำหลัก "typename"
คำตอบคือ: เราตัดสินใจว่าคอมไพเลอร์ควรแยกวิเคราะห์นี้อย่างไร หากt::x
เป็นชื่อที่ขึ้นต่อกันเราจำเป็นต้องนำหน้าด้วยtypename
เพื่อบอกคอมไพเลอร์ให้วิเคราะห์คำด้วยวิธีใดวิธีหนึ่ง มาตรฐานบอกว่าที่ (14.6 / 2):
ชื่อที่ใช้ในการประกาศหรือคำนิยามเทมเพลตและที่ขึ้นอยู่กับเท็มเพลตพารามิเตอร์จะถือว่าไม่ตั้งชื่อประเภทยกเว้นว่าการค้นหาชื่อที่ใช้บังคับพบว่าชื่อประเภทหรือชื่อที่มีคุณสมบัติโดยพิมพ์ชื่อคำหลัก
มีหลายชื่อที่typename
ไม่จำเป็นเนื่องจากคอมไพเลอร์สามารถใช้ชื่อการค้นหาที่เกี่ยวข้องในการกำหนดแม่แบบคิดออกว่าจะแยกวิเคราะห์การสร้างตัวเอง - ตัวอย่างเช่นด้วยT *f;
เมื่อT
เป็นพารามิเตอร์แม่แบบประเภท แต่สำหรับที่จะประกาศจะต้องมีการเขียนเป็นt::x * f;
typename t::x *f;
หากคุณไม่ใช้คำหลักและชื่อนั้นเป็นประเภทที่ไม่ใช่ แต่เมื่อเริ่มต้นพบว่ามันหมายถึงประเภทข้อความผิดพลาดตามปกติจะถูกปล่อยออกมาโดยคอมไพเลอร์ บางครั้งข้อผิดพลาดจะถูกกำหนดตามเวลาที่กำหนด:
// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;
ไวยากรณ์อนุญาตให้typename
ใช้ก่อนชื่อที่ผ่านการรับรองเท่านั้นซึ่งจะถูกนำมาใช้ตามที่ได้รับอนุญาตซึ่งชื่อที่ไม่ผ่านการรับรองนั้นเป็นที่รู้จักกันเสมอในการอ้างถึงชนิดหากพวกมันเป็นเช่นนั้น
gotcha ที่คล้ายกันมีอยู่สำหรับชื่อที่แสดงถึงเทมเพลตตามที่บอกไว้โดยข้อความเกริ่นนำ
คำหลัก "เทมเพลต"
จำคำพูดเริ่มต้นด้านบนและวิธีมาตรฐานต้องการการจัดการพิเศษสำหรับแม่แบบเช่นกัน? มาดูตัวอย่างที่ไร้เดียงสาต่อไปนี้:
boost::function< int() > f;
อาจดูเหมือนมนุษย์ผู้อ่าน ไม่ใช่สำหรับคอมไพเลอร์ ลองนึกภาพนิยามโดยพลการของboost::function
และf
:
namespace boost { int function = 0; }
int main() {
int f = 0;
boost::function< int() > f;
}
นั่นเป็นนิพจน์ที่ถูกต้องจริงๆ! จะใช้น้อยกว่าผู้ประกอบการที่จะเปรียบเทียบboost::function
กับศูนย์ ( int()
) แล้วใช้ที่มากขึ้นกว่าผู้ประกอบการที่จะเปรียบเทียบที่เกิดขึ้นกับbool
f
อย่างไรก็ตามอย่างที่คุณรู้boost::function
ในชีวิตจริงเป็นเทมเพลตดังนั้นคอมไพเลอร์จึงรู้ (14.2 / 3):
หลังจากการค้นหาชื่อ (3.4) พบว่าชื่อนั้นเป็นชื่อเทมเพลตหากชื่อนี้ตามมาด้วย <, <จะถูกนำมาเป็นจุดเริ่มต้นของเทมเพลตอาร์กิวเมนต์รายการเสมอและไม่เคยเป็นชื่อที่ตามมาด้วยการน้อยกว่า - กว่าผู้ประกอบการ
typename
ตอนนี้เราจะกลับไปที่ปัญหาเช่นเดียวกับที่มี จะเป็นอย่างไรถ้าเรายังไม่รู้ว่าชื่อนั้นเป็นเทมเพลตเมื่อทำการแยกวิเคราะห์รหัสหรือไม่ เราจะต้องใส่ทันทีก่อนที่ชื่อแม่แบบตามที่ระบุโดยtemplate
14.2/4
ดูเหมือนว่า:
t::template f<int>(); // call a function template
ชื่อเทมเพลตไม่เพียง แต่จะเกิดขึ้นหลังจาก::
แต่ยังหลังจาก->
หรือ.
ในการเข้าถึงสมาชิกระดับ คุณต้องแทรกคำหลักที่นั่นด้วย:
this->template f<int>(); // call a function template
การอ้างอิง
สำหรับคนที่มีหนังสือ Standardese หนา ๆ บนชั้นวางของและต้องการทราบว่าฉันกำลังพูดถึงอะไรฉันจะพูดถึงเรื่องนี้ที่ระบุไว้ใน Standard
ในการประกาศแม่แบบโครงสร้างบางอย่างมีความหมายที่แตกต่างกันขึ้นอยู่กับข้อโต้แย้งแม่แบบที่คุณใช้ในการยกตัวอย่างแม่แบบ: การแสดงออกอาจมีประเภทหรือค่าที่แตกต่างกันตัวแปรอาจมีประเภทที่แตกต่างกันหรือการเรียกฟังก์ชั่น โครงสร้างดังกล่าวโดยทั่วไปจะกล่าวว่าขึ้นอยู่กับพารามิเตอร์แม่แบบ
มาตรฐานกำหนดกฎอย่างแม่นยำโดยการสร้างขึ้นอยู่กับหรือไม่ มันแยกพวกมันออกเป็นกลุ่มต่าง ๆ อย่างมีเหตุมีผล: ประเภทหนึ่งประเภทหนึ่งและอีกกลุ่มหนึ่งแสดงออก นิพจน์อาจขึ้นอยู่กับมูลค่าและ / หรือประเภทของพวกเขา ดังนั้นเราจึงมีตัวอย่างทั่วไปต่อท้าย:
- ประเภทที่อ้างถึง (เช่น: พารามิเตอร์เทมเพลตชนิด
T
)
- นิพจน์ที่ขึ้นกับมูลค่า (เช่น: พารามิเตอร์เทมเพลตที่ไม่ใช่ประเภท
N
)
- นิพจน์ที่ขึ้นกับประเภท (เช่น: การส่งไปยังพารามิเตอร์เทมเพลตชนิด
(T)0
)
กฎส่วนใหญ่นั้นใช้งานง่ายและสร้างขึ้นแบบเรียกซ้ำ: ตัวอย่างชนิดที่สร้างT[N]
ขึ้นตามประเภทที่ขึ้นกับว่าN
เป็นนิพจน์ที่ขึ้นกับมูลค่าหรือT
เป็นประเภทที่ขึ้นต่อกัน รายละเอียดของสิ่งนี้สามารถอ่านได้ในส่วน(14.6.2/1
) สำหรับชนิดที่ขึ้นต่อกัน(14.6.2.2)
สำหรับนิพจน์ที่ขึ้นอยู่กับประเภทและ(14.6.2.3)
สำหรับนิพจน์ที่ขึ้นกับมูลค่า
ขึ้นอยู่กับชื่อ
มาตรฐานเป็นบิตไม่มีความชัดเจนเกี่ยวกับสิ่งที่ว่าเป็นชื่อที่ขึ้นอยู่กับ อ่านง่าย (คุณรู้ว่าหลักการของความประหลาดใจน้อยที่สุด) ทั้งหมดที่กำหนดเป็นชื่อขึ้นอยู่กับเป็นกรณีพิเศษสำหรับชื่อฟังก์ชั่นด้านล่าง แต่เนื่องจากT::x
ต้องมีการค้นหาอย่างชัดเจนในบริบทการสร้างอินสแตนซ์จึงต้องเป็นชื่อที่ต้องพึ่งพา (โชคดีที่ในช่วงกลาง C ++ 14 คณะกรรมการเริ่มมองหาวิธีแก้ไขคำจำกัดความที่สับสนนี้)
เพื่อหลีกเลี่ยงปัญหานี้ฉันใช้วิธีการแปลข้อความมาตรฐานอย่างง่าย ของการสร้างทั้งหมดที่แสดงถึงชนิดหรือนิพจน์ที่สัมพันธ์กันเซ็ตย่อยของพวกมันจะแสดงชื่อ ดังนั้นชื่อเหล่านั้นจึงเป็น "ชื่อที่ต้องพึ่งพา" ชื่อสามารถมีรูปแบบที่แตกต่าง - มาตรฐานพูดว่า:
ชื่อคือการใช้ตัวระบุ (2.11), operator-function-id (13.5), conversion-function-id (12.3.2) หรือ template-id (14.2) ที่แสดงถึงเอนทิตีหรือป้ายกำกับ (6.6.4, 6.1)
ตัวระบุเป็นเพียงลำดับตัวอักษรธรรมดา / หลักในขณะที่อีกสองคือรูปแบบoperator +
และ รูปแบบสุดท้ายคือoperator type
template-name <argument list>
ทั้งหมดเหล่านี้เป็นชื่อและโดยทั่วไปแล้วใช้ในมาตรฐานชื่อยังสามารถรวมตัวระบุที่บอกชื่อเนมสเปซหรือคลาสที่ควรค้นหา
นิพจน์ที่ขึ้นกับค่า1 + N
ไม่ใช่ชื่อ แต่N
เป็น ชุดย่อยของโครงสร้างที่ขึ้นต่อกันทั้งหมดที่มีชื่อเรียกว่าชื่อที่ขึ้นต่อกัน อย่างไรก็ตามชื่อฟังก์ชั่นอาจมีความหมายแตกต่างกันในอินสแตนซ์ที่ต่างกันของเทมเพลต แต่น่าเสียดายที่กฎทั่วไปนี้ไม่ถูกตรวจจับ
ชื่อฟังก์ชั่นขึ้นอยู่กับ
ไม่ใช่ความกังวลของบทความนี้เป็นหลัก แต่ก็ยังมีมูลค่าการกล่าวขวัญ: ชื่อฟังก์ชั่นเป็นข้อยกเว้นที่ได้รับการจัดการแยกต่างหาก ชื่อฟังก์ชั่นตัวระบุไม่ได้ขึ้นอยู่กับตัวมันเอง แต่โดยการแสดงออกของอาร์กิวเมนต์ชนิดที่ขึ้นอยู่กับที่ใช้ในการโทร ในตัวอย่างf((T)0)
, f
เป็นชื่อที่ขึ้นอยู่กับ (14.6.2/1)
ในมาตรฐานนี้จะระบุไว้
หมายเหตุเพิ่มเติมและตัวอย่าง
ในกรณีที่เราต้องพอทั้งสองและtypename
template
รหัสของคุณควรมีลักษณะดังนี้
template <typename T, typename Tail>
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
typedef typename Tail::template inUnion<U> dummy;
};
// ...
};
คำหลักtemplate
ไม่จำเป็นต้องปรากฏในส่วนสุดท้ายของชื่อเสมอไป สามารถปรากฏตรงกลางก่อนชื่อคลาสที่ใช้เป็นขอบเขตเช่นในตัวอย่างต่อไปนี้
typename t::template iterator<int>::value_type v;
ในบางกรณีคำหลักถูกห้ามดังรายละเอียดด้านล่าง
typename
ที่ชื่อของชั้นฐานขึ้นอยู่กับคุณไม่ได้รับอนุญาตให้เขียน มันสันนิษฐานว่าชื่อที่ให้ไว้เป็นชื่อประเภทชั้นเรียน สิ่งนี้เป็นจริงสำหรับทั้งสองชื่อในรายการคลาสฐานและรายการตัวสร้างเริ่มต้น:
template <typename T>
struct derive_from_Has_type : /* typename */ SomeBase<T>::type
{ };
ในการใช้ - ประกาศเป็นไปไม่ได้ที่จะใช้template
หลังจากที่ผ่าน::
มาและคณะกรรมการ C ++ บอกว่าจะไม่ทำงานในการแก้ปัญหา
template <typename T>
struct derive_from_Has_type : SomeBase<T> {
using SomeBase<T>::template type; // error
using typename SomeBase<T>::type; // typename *is* allowed
};