คำแนะนำการหักเทมเพลตคืออะไรและเราควรใช้เมื่อใด


89

มาตรฐาน C ++ 17 แนะนำ "คำแนะนำการหักเทมเพลต" ฉันรวบรวมสิ่งเหล่านี้เกี่ยวกับการหักอาร์กิวเมนต์แม่แบบใหม่สำหรับตัวสร้างที่นำมาใช้ในมาตรฐานเวอร์ชันนี้ แต่ฉันยังไม่เห็นคำอธิบายง่ายๆสไตล์คำถามที่พบบ่อยว่าพวกเขาคืออะไรและมีไว้เพื่ออะไร

  • คำแนะนำการหักเทมเพลตใน C ++ 17 คืออะไร?

  • ทำไม (และเมื่อ) เราต้องการพวกเขา?

  • ฉันจะประกาศได้อย่างไร?



โดยเฉพาะอย่างยิ่งฉันสนใจที่จะทราบว่าคู่มือการหักเงินใด ๆ ที่จัดทำโดย C ++ 17 STL (เช่นสำหรับ std :: pair หรือ std :: tuple) รายการเทมเพลตมาตรฐาน "ที่หักล้างได้" ทั้งหมดใน C ++ 17 คืออะไร
Quuxplusone


ฉันสนใจที่จะทราบว่าคอมไพเลอร์รองรับสิ่งนี้หรือไม่ ฉันลอง gcc, clang และ vc ++ rextester.com/DHPHC32332ไม่เป็นไรฉันพบว่ามันใช้ได้เฉพาะกับ gc ++ 8.1 C ++ 17 และ 2a g ++ -std = c ++ 17 -O2 -Wall -pedantic -pthread main.cpp &&./a.out
Jean-Simon Brochu

คำตอบ:


102

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

ตัวอย่างที่ง่ายที่สุดคือของstd::vectorและตัวสร้างที่รับคู่ตัววนซ้ำ

template<typename Iterator>
void func(Iterator first, Iterator last)
{
  vector v(first, last);
}

คอมไพเลอร์ต้องการที่จะคิดออกว่าvector<T>'s Tประเภทจะได้รับ เรารู้ว่าคำตอบคืออะไร ควรจะเป็นT typename std::iterator_traits<Iterator>::value_typeแต่เราจะบอกคอมไพเลอร์ได้อย่างไรโดยไม่ต้องพิมพ์vector<typename std::iterator_traits<Iterator>::value_type>?

คุณใช้คู่มือการหักเงิน:

template<typename Iterator> vector(Iterator b, Iterator e) -> 
    vector<typename std::iterator_traits<Iterator>::value_type>;

นี้จะบอกคอมไพเลอร์ที่ว่าเมื่อคุณเรียกvectorที่ตรงกับคอนสตรัคว่ารูปแบบก็จะอนุมานเชี่ยวชาญใช้รหัสที่อยู่ด้านขวาของvector->

คุณต้องการคำแนะนำเมื่อการหักประเภทออกจากอาร์กิวเมนต์ไม่ได้ขึ้นอยู่กับประเภทของอาร์กิวเมนต์เหล่านั้น เริ่มต้นvectorจากinitializer_listอย่างชัดเจนใช้vector's Tดังนั้นจึงไม่จำเป็นต้องแนะนำ

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

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

นอกจากนี้ยังหมายความว่าคุณสามารถใช้คำแนะนำที่มีการรวมและการเริ่มต้นรวม:

template<typename T>
struct Thingy
{
  T t;
};

Thingy(const char *) -> Thingy<std::string>;

Thingy thing{"A String"}; //thing.t is a `std::string`.

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


7
อืมมันเพิ่งเกิดขึ้นกับฉันแม้จะมีไกด์ แต่vector v{first, last};ก็ไม่ได้ทำในสิ่งที่ถูกต้อง :(
TC

3
@TC …เว้นแต่สิ่งที่ถูกต้องคือการสร้างเวกเตอร์ของตัวทำซ้ำ และstd::string{32,'*'}[0] == ' '(สำหรับ ASCII) แต่ทั้งหมดนี้เป็นจริงตั้งแต่ C ++ 11
Arne Vogel

2
เกิดอะไรขึ้นกับพารามิเตอร์เวกเตอร์ตัวจัดสรร จะเกิดอะไรขึ้นหากพารามิเตอร์เวกเตอร์ตัวจัดสรรไม่มีอาร์กิวเมนต์เริ่มต้น (คุณไม่สามารถอนุมานได้จาก InputIterator)
gnzlbg

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

1
@NicolBolas: ฉันเข้าใจ ไม่ชัดเจนสำหรับฉันว่าคำถามเกี่ยวกับคำแนะนำการหักเงินอย่างชัดเจนเลย ... ฉันคิดว่ามันจะมีประโยชน์ถ้าคุณใส่สิ่งที่คุณเขียนตามตัวอักษรในความคิดเห็นนี้
user541686
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.