Hành vi C ++ 20 phá vỡ mã hiện có với toán tử bình đẳng?
Tôi đã gặp phải vấn đề này trong khi gỡ lỗi câu hỏi này .
Tôi đã cắt nó xuống bằng mọi cách để chỉ sử dụng các Toán tử Boost :
Trình biên dịch 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 }
Chương trình này biên dịch và chạy tốt với C ++ 17 (đã bật ubsan / asan) trong cả GCC và Clang.
Khi bạn thay đổi hàm tạo ngầm định thành
explicit
, các dòng có vấn đề rõ ràng không còn được biên dịch trên C ++ 17 nữa
Đáng ngạc nhiên là cả hai phiên bản đều biên dịch trên C ++ 20 ( v1 và v2 ) , nhưng chúng dẫn đến đệ quy vô hạn (sự cố hoặc vòng lặp chặt chẽ, tùy thuộc vào mức độ tối ưu hóa) trên hai dòng không biên dịch trên C ++ 17.
Rõ ràng là loại lỗi âm thầm len lỏi vào khi nâng cấp lên C ++ 20 là điều đáng lo ngại.
Câu hỏi:
- Đây có phải là hành vi tuân thủ c ++ 20 không (tôi mong đợi như vậy)
- Chính xác thì can thiệp là gì? Tôi nghi ngờ nó có thể là do hỗ trợ "toán tử phi thuyền" mới của c ++ 20, nhưng không hiểu nó thay đổi hành vi của mã này như thế nào .
Trả lời
Thật vậy, thật không may, C ++ 20 làm cho mã này trở nên đệ quy vô hạn.
Đây là một ví dụ rút gọn:
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;
};
Hãy chỉ nhìn vào 42 == F{42}
.
Trong C ++ 17, chúng tôi chỉ có một ứng cử viên: ứng viên không phải thành viên ( #2
), vì vậy chúng tôi chọn nó. Phần thân của nó x == y
, bản thân nó chỉ có một ứng cử viên: ứng viên thành viên ( #1
) liên quan đến việc chuyển đổi ngầm y
thành một F
. Và sau đó ứng viên thành viên đó so sánh hai thành viên nguyên và điều này hoàn toàn ổn.
Trong C ++ 20, biểu thức ban đầu 42 == F{42}
bây giờ có hai ứng cử viên: cả ứng viên không phải thành viên ( #2
) như trước và bây giờ cũng là ứng viên thành viên được #1
đảo ngược ( đã đảo ngược). #2
là đối sánh tốt hơn - chúng tôi đối sánh chính xác cả hai đối số thay vì gọi một chuyển đổi, vì vậy nó được chọn.
Tuy nhiên, x == y
bây giờ có hai ứng cử viên: ứng cử viên thành viên lại ( #1
), và ứng viên không phải là thành viên #2
đã đảo ngược ( đã đảo ngược). #2
là đối sánh tốt hơn một lần nữa vì cùng một lý do rằng trước đây là đối sánh tốt hơn: không cần chuyển đổi. Vì vậy, chúng tôi đánh giá y == x
thay thế. Đệ quy vô hạn.
Các ứng cử viên không bị đảo ngược được ưu tiên hơn các ứng viên bị đảo ngược, nhưng chỉ như một yếu tố quyết định. Trình tự chuyển đổi tốt hơn luôn là đầu tiên.
Được rồi, tuyệt vời, chúng ta có thể sửa nó như thế nào? Tùy chọn đơn giản nhất là loại bỏ hoàn toàn ứng viên không phải là thành viên:
struct F {
/*implicit*/ F(int t_) : t(t_) {}
bool operator==(F const& o) const { return t == o.t; }
private:
int t;
};
42 == F{42}
ở đây đánh giá là F{42}.operator==(42)
, hoạt động tốt.
Nếu chúng tôi muốn giữ lại ứng viên không phải là thành viên, chúng tôi có thể thêm ứng viên đã đảo ngược của nó một cách rõ ràng:
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;
};
Điều này làm cho 42 == F{42}
vẫn chọn ứng cử viên không phải thành viên, nhưng bây giờ x == y
trong cơ thể sẽ thích ứng viên thành viên hơn, điều này sau đó thực hiện bình đẳng thông thường.
Phiên bản cuối cùng này cũng có thể xóa ứng cử viên không phải là thành viên. Phần sau cũng hoạt động mà không cần đệ quy cho tất cả các trường hợp thử nghiệm (và là cách tôi sẽ viết so sánh trong C ++ 20 về sau):
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;
};