C ++ 20 พฤติกรรมทำลายรหัสที่มีอยู่ด้วยตัวดำเนินการความเท่าเทียมกัน?
ฉันพบปัญหานี้ในขณะที่แก้ไขข้อบกพร่องของคำถามนี้
ฉันตัดมันลงจนสุดเพื่อใช้Boost Operators :
คอมไพเลอร์ Explorer C ++ 17 C ++ 20
#include <boost/operators.hpp> struct F : boost::totally_ordered1<F, boost::totally_ordered2<F, int>> { /*implicit*/ F(int t_) : t(t_) {} bool operator==(F const& o) const { return t == o.t; } bool operator< (F const& o) const { return t < o.t; } private: int t; }; int main() { #pragma GCC diagnostic ignored "-Wunused" F { 42 } == F{ 42 }; // OKAY 42 == F{42}; // C++17 OK, C++20 infinite recursion F { 42 } == 42; // C++17 OK, C++20 infinite recursion }
โปรแกรมนี้รวบรวมและทำงานได้ดีด้วย C ++ 17 (เปิดใช้งาน ubsan / asan) ทั้งใน GCC และ Clang
เมื่อคุณเปลี่ยนตัวสร้างโดยนัย
explicit
เป็นบรรทัดที่มีปัญหาจะไม่คอมไพล์บน C ++ 17 อีกต่อไป
น่าแปลกใจที่ทั้งสองเวอร์ชันคอมไพล์บน C ++ 20 ( v1และv2 )แต่นำไปสู่การเรียกซ้ำแบบไม่มีที่สิ้นสุด (ความผิดพลาดหรือการวนซ้ำขึ้นอยู่กับระดับการปรับให้เหมาะสม) บนสองบรรทัดที่จะไม่รวบรวมใน C ++ 17
เห็นได้ชัดว่าข้อผิดพลาดเงียบประเภทนี้กำลังคืบคลานเข้ามาโดยการอัปเกรดเป็น C ++ 20 นั้นน่าเป็นห่วง
คำถาม:
- เป็นไปตามลักษณะการทำงานของ c ++ 20 หรือไม่ (ฉันคาดหวังเช่นนั้น)
- รบกวนอะไรกันแน่? ฉันสงสัยว่าอาจเป็นเพราะการสนับสนุน "ยานอวกาศ" ใหม่ของ c ++ 20 แต่ไม่เข้าใจว่ามันเปลี่ยนพฤติกรรมของโค้ดนี้อย่างไร
คำตอบ
อันที่จริง C ++ 20 น่าเสียดายที่ทำให้รหัสนี้วนซ้ำไม่สิ้นสุด
นี่คือตัวอย่างที่ลดลง:
struct F {
/*implicit*/ F(int t_) : t(t_) {}
// member: #1
bool operator==(F const& o) const { return t == o.t; }
// non-member: #2
friend bool operator==(const int& y, const F& x) { return x == y; }
private:
int t;
};
42 == F{42}
ให้ดูเพียงแค่
ใน C ++ 17 เรามีผู้สมัครเพียงคนเดียว: ผู้สมัครที่ไม่ใช่สมาชิก ( #2
) ดังนั้นเราจึงเลือกสิ่งนั้น ตัวของมันx == y
เองมีผู้สมัครเพียงคนเดียว: ผู้สมัครสมาชิก ( #1
) ซึ่งเกี่ยวข้องกับการแปลงโดยปริยายy
เป็นไฟล์F
. จากนั้นผู้สมัครสมาชิกนั้นจะเปรียบเทียบสมาชิกจำนวนเต็มสองตัวและนี่ก็ใช้ได้โดยสิ้นเชิง
ใน C ++ 20 นิพจน์เริ่มต้น42 == F{42}
มีผู้สมัครสองคน: ทั้งผู้สมัครที่ไม่ใช่สมาชิก ( #2
) เหมือนก่อนหน้านี้และตอนนี้ยังเป็นผู้สมัครสมาชิกที่#1
กลับรายการ( ย้อนกลับ) #2
เป็นการจับคู่ที่ดีกว่า - เราจับคู่อาร์กิวเมนต์ทั้งสองแทนการเรียกใช้การแปลงดังนั้นจึงถูกเลือก
อย่างไรก็ตามx == y
ตอนนี้มีผู้สมัครสองคน: ผู้สมัครสมาชิกอีกครั้ง ( #1
) แต่ยังมีผู้สมัครที่ไม่ได้เป็นสมาชิกที่#2
กลับรายการด้วย( ย้อนกลับ) #2
เป็นการจับคู่ที่ดีกว่าอีกครั้งด้วยเหตุผลเดียวกับที่เคยเป็นคู่ที่ดีกว่าก่อนหน้านี้: ไม่จำเป็นต้องมีการแปลง ดังนั้นเราจึงประเมินy == x
แทน การเรียกซ้ำที่ไม่มีที่สิ้นสุด
ผู้สมัครที่ไม่กลับรายการเป็นที่ต้องการในการกลับตัวของผู้สมัคร แต่เป็นเพียงตัวผูก ลำดับการแปลงที่ดีขึ้นเป็นอันดับแรกเสมอ
โอเคดีเราจะแก้ไขได้อย่างไร ตัวเลือกที่ง่ายที่สุดคือการลบผู้สมัครที่ไม่ใช่สมาชิกทั้งหมด:
struct F {
/*implicit*/ F(int t_) : t(t_) {}
bool operator==(F const& o) const { return t == o.t; }
private:
int t;
};
42 == F{42}
ที่นี่ประเมินว่าF{42}.operator==(42)
ซึ่งใช้งานได้ดี
หากเราต้องการเก็บผู้สมัครที่ไม่ใช่สมาชิกไว้เราสามารถเพิ่มผู้สมัครที่กลับรายการได้อย่างชัดเจน:
struct F {
/*implicit*/ F(int t_) : t(t_) {}
bool operator==(F const& o) const { return t == o.t; }
bool operator==(int i) const { return t == i; }
friend bool operator==(const int& y, const F& x) { return x == y; }
private:
int t;
};
สิ่งนี้ทำให้42 == F{42}
ยังคงเลือกผู้สมัครที่ไม่ใช่สมาชิก แต่ตอนนี้x == y
ในร่างกายจะชอบผู้สมัครสมาชิกซึ่งจะทำให้เกิดความเท่าเทียมกันตามปกติ
เวอร์ชันสุดท้ายนี้ยังสามารถลบผู้สมัครที่ไม่ใช่สมาชิกได้ สิ่งต่อไปนี้ยังใช้งานได้โดยไม่มีการเรียกซ้ำสำหรับกรณีทดสอบทั้งหมด (และเป็นวิธีที่ฉันจะเขียนการเปรียบเทียบใน C ++ 20 ต่อไป):
struct F {
/*implicit*/ F(int t_) : t(t_) {}
bool operator==(F const& o) const { return t == o.t; }
bool operator==(int i) const { return t == i; }
private:
int t;
};