같음 연산자로 기존 코드를 깨는 C ++ 20 동작?
이 질문 을 디버깅하는 동안 이 문제가 발생했습니다 .
Boost Operators를 사용하기 위해 끝까지 정리했습니다 .
컴파일러 탐색기 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 }
이 프로그램은 GCC와 Clang 모두에서 C ++ 17 (ubsan / asan 활성화 됨)로 잘 컴파일되고 실행됩니다.
암시 적 생성자를으로 변경하면
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;
};