เหตุใดจึงเลือกตัวดำเนินการแปลงเกินพิกัดนี้


27

พิจารณารหัสต่อไปนี้

struct any
{
    template <typename T>
    operator T &&() const;

    template <typename T>
    operator T &() const;
};
int main()
{
    int a = any{};
}

ที่นี่ผู้ประกอบการแปลงที่สองถูกเลือกโดยความละเอียดเกินพิกัด ทำไม?

เท่าที่ฉันเข้าใจผู้ประกอบการทั้งสองจะอนุมานoperator int &&() constและoperator int &() constตามลำดับ ทั้งสองอยู่ในชุดของฟังก์ชันที่ทำงานได้ การอ่านผ่าน [over.match.best] ไม่ได้ช่วยฉันหาสาเหตุว่าทำไมสิ่งหลังถึงดีกว่า

เหตุใดฟังก์ชันหลังจึงดีกว่ารุ่นก่อน


ฉันเป็นแมวที่เรียบง่ายและฉันจะบอกว่ามันจะต้องเป็นครั้งที่สองอีกครั้งการแนะนำของคนอื่นจะได้รับการเปลี่ยนแปลงที่ทำลายใน C ++ 11 มันจะอยู่ในมาตรฐานที่ไหนสักแห่ง แม้ว่าคำถามที่ดีมี upvote (ผู้เชี่ยวชาญให้ความสนใจ: ถ้าความคิดเห็นนี้เป็น hogwash โปรดบอกฉัน!)
Bathsheba

3
FWIW template <typename T> operator T &&() const &&; template <typename T> operator T &() const &;รับสายเพื่อเรียกสายแรก
NathanOliver

5
@LightnessRaceswithMonica ตัวดำเนินการแปลงอนุญาตมิฉะนั้นจะไม่มีวิธีที่จะมีตัวดำเนินการแปลงหลายตัว
NathanOliver

1
@Bathsheba ฉันไม่คิดว่าเป็นเหตุผลว่าทำไม นั่นคือการบอกว่าตัวสร้างการเคลื่อนย้ายไม่สามารถถูกเลือกได้ด้วยความละเอียดที่มากเกินไปเพราะมันจะเป็นการเปลี่ยนแปลงครั้งใหญ่ หากคุณเขียนตัวสร้างการย้ายคุณกำลังเลือกที่จะใช้รหัสของคุณว่า "เสีย" ตามคำจำกัดความนั้น
Brian

1
@LightnessRaceswithMonica วิธีที่ฉันเก็บไว้ในหัวของฉันคือฉันปฏิบัติต่อประเภทกลับเป็นชื่อของฟังก์ชั่น ประเภทที่แตกต่างกันเท่ากับชื่อที่แตกต่างกันเท่ากับความสำเร็จ :)
NathanOliver

คำตอบ:


13

ผู้ประกอบการที่มีการแปลงผลตอบแทนเป็นที่ต้องการเพราะมันมีความเชี่ยวชาญมากกว่าผู้ประกอบการที่มีการแปลงผลตอบแทนT&T&&

ดู C ++ 17 [temp.deduct.partial] / (3.2):

ในบริบทของการเรียกไปยังฟังก์ชันการแปลงจะใช้ชนิดการส่งคืนของแม่แบบฟังก์ชันการแปลง

และ / 9:

หากสำหรับประเภทที่กำหนดการหักค่าจะสำเร็จทั้งสองทิศทาง (เช่นชนิดจะเหมือนกันหลังจากการแปลงด้านบน) และทั้งคู่PและAเป็นประเภทการอ้างอิง (ก่อนที่จะถูกแทนที่ด้วยประเภทที่อ้างถึงด้านบน): - ถ้าประเภทจากเทมเพลตอาร์กิวเมนต์ เป็นการอ้างอิง lvalue และชนิดจากเท็มเพลตพารามิเตอร์ไม่ได้ชนิดของพารามิเตอร์นั้นไม่ได้รับการพิจารณาว่ามีความเชี่ยวชาญเป็นพิเศษเป็นประเภทอาร์กิวเมนต์ ...


12

ตัวดำเนินการแปลงค่าส่งคืนที่อนุมานนั้นค่อนข้างแปลก แต่แนวคิดหลักคือมันทำหน้าที่เหมือนพารามิเตอร์ฟังก์ชั่นเพื่อเลือกอันไหนที่ใช้

และเมื่อตัดสินใจระหว่างT&&และชนะในกฎระเบียบมติเกินพิกัด นี่คือการอนุญาต:T&T&

template<class T>
void f( T&& ) { std::cout << "rvalue"; }
template<class T>
void f( T& ) { std::cout << "lvalue"; }

ไปทำงาน. T&&สามารถจับคู่กับ lvalue ได้ แต่เมื่อทั้ง lvalue และ universal reference overload มีอยู่ lvalue อันที่ต้องการ

ชุดของตัวดำเนินการแปลงที่เหมาะสมน่าจะเป็น:

template <typename T>
operator T&&() &&;

template <typename T>
operator T &() const; // maybe &

หรือแม้กระทั่ง

template <typename T>
operator T() &&;

template <typename T>
operator T &() const; // maybe &

เพื่อป้องกันการยืดอายุการใช้งานที่ล้มเหลวจากการกัดคุณ

3 ประเภทที่ใช้ในการกำหนดการสั่งซื้อขึ้นอยู่กับบริบทที่สั่งซื้อบางส่วนเสร็จสิ้น:

[SNIP]

(3.2) ในบริบทของการเรียกไปยังฟังก์ชันการแปลงจะใช้ชนิดการส่งคืนของแม่แบบฟังก์ชันการแปลง

ซึ่งจะจบลงที่ขึ้นอยู่กับกฎ "พิเศษเพิ่มเติม" เมื่อเลือกโอเวอร์โหลด:

(9.1) ถ้าชนิดจากเท็มเพลตอาร์กิวเมนต์เป็นข้อมูลอ้างอิงแบบ lvalue และชนิดจากเท็มเพลตพารามิเตอร์ไม่ประเภทของพารามิเตอร์นั้นไม่ได้รับการพิจารณาว่ามีความเชี่ยวชาญอย่างน้อยเท่ากับชนิดอาร์กิวเมนต์ มิฉะนั้น,

ดังนั้นจึงoperator T&&ไม่ได้อย่างน้อยเป็นความเชี่ยวชาญเป็นoperator T&ขณะที่รัฐไม่มีกฎoperator T&ไม่ได้อย่างน้อยเป็นความเชี่ยวชาญเป็นoperator T&&เพื่อให้มีความเชี่ยวชาญมากกว่าoperator T&operator T&&

เทมเพลตที่เชี่ยวชาญมากขึ้นจะได้รับการแก้ไขที่เกินความต้องการน้อยกว่าทุกสิ่งที่เท่ากัน


มีคนเพิ่มแท็กทนายความภาษาขณะที่ฉันตอบ ฉันสามารถลบได้ ฉันมีหน่วยความจำที่คลุมเครือในการอ่านข้อความที่ระบุว่าเป็นพารามิเตอร์ที่จะเลือกอันใดอันหนึ่งที่ถูกเรียกและข้อความที่พูดถึงวิธีการที่จะได้รับการปฏิบัติเมื่อ&&เทียบกับ&การเลือกเทมเพลตโอเวอร์โหลด .
Yakk - Adam Nevraumont

4

เราพยายามที่จะเริ่มต้นจากint anyกระบวนการในการที่:

  1. คิดหาวิธีที่เราสามารถทำได้ นั่นคือตรวจสอบผู้สมัครของเราทั้งหมด สิ่งเหล่านี้มาจากฟังก์ชั่นการแปลงที่ไม่ชัดเจนซึ่งสามารถแปลงintเป็นลำดับการแปลงมาตรฐาน ( [over.match.conv] ) ส่วนนี้มีวลีนี้:

    การเรียกฟังก์ชั่นการแปลงที่ส่งคืน“ การอ้างอิงถึงX” เป็นประเภทที่Xมีค่าน้อยดังนั้นฟังก์ชั่นการแปลงดังกล่าวจึงถูกพิจารณาว่าให้ผลตอบแทนXสำหรับกระบวนการนี้ในการเลือกฟังก์ชั่นผู้สมัคร

  2. เลือกผู้สมัครที่ดีที่สุด

หลังจากขั้นตอนที่ 1 เรามีผู้สมัครสองคน operator int&() constและoperator int&&() constทั้งสองอย่างนี้ได้รับการพิจารณาว่าให้ผลintตามวัตถุประสงค์ในการเลือกฟังก์ชั่นผู้สมัคร ตัวเลือกใดที่ให้ผลดีที่สุดint ?

เรามี tiebreaker ที่ชอบการอ้างอิง lvalue กับการอ้างอิง rvalue ( [over.ics.rank] /3.2.3 ) เราไม่ได้ผูกการอ้างอิงที่นี่จริง ๆ และตัวอย่างมีการคว่ำค่อนข้าง - สำหรับกรณีที่พารามิเตอร์เป็นการอ้างอิง lvalue vs rvalue

หากไม่ได้ผลเราจะผ่านไปที่[over.match.best] /2.5 tiebreaker เพื่อเลือกแม่แบบฟังก์ชั่นพิเศษเพิ่มเติม

โดยทั่วไปกฎของหัวแม่มือคือการแปลงที่เฉพาะเจาะจงมากขึ้นคือการจับคู่ที่ดีที่สุด ฟังก์ชั่นการแปลงอ้างอิง lvalue มีความเฉพาะเจาะจงมากกว่าฟังก์ชั่นการแปลงอ้างอิงการส่งต่อดังนั้นจึงเป็นที่ต้องการ ไม่มีอะไรเกี่ยวกับการที่intเรากำลังเริ่มต้นที่ต้องการ rvalue (ถ้าเราแทนการเริ่มต้นint&&แล้วoperator T&() constจะไม่ได้รับการสมัคร)


3.2.3 จริง ๆ แล้วบอกว่าT&&ชนะไม่ได้ อา แต่เราไม่ได้มีค่า rvalue หรือพวกเรา เมื่อเลือกผู้สมัครของเราคำบางคำจะต้องกำหนดวิธีที่เราได้รับint&และint&&นั่นคือความผูกพัน?
Yakk - Adam Nevraumont

@ Yakk-AdamNevraumont ไม่ว่าใครชอบอ้างอิง lvalue กับการอ้างอิง rvalue ฉันคิดว่าน่าจะเป็นส่วนที่ผิดอยู่ดีและ tiebreaker ที่ "มีความเชี่ยวชาญมากกว่า" เป็นสิ่งที่ถูกต้อง
Barry
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.