[c++] 부울이 아닌 반환 값과 동등 비교를 오버로드 할 때 C ++ 20의 변경 사항 또는 clang-trunk / gcc-trunk의 회귀가 변경됩니까?

다음 코드는 c ++ 17 모드에서 clang-trunk로 잘 컴파일되지만 c ++ 2a (다가오는 c ++ 20) 모드에서 중단됩니다.

// Meta struct describing the result of a comparison
struct Meta {};

struct Foo {
    Meta operator==(const Foo&) {return Meta{};}
    Meta operator!=(const Foo&) {return Meta{};}
};

int main()
{
    Meta res = (Foo{} != Foo{});
}

또한 gcc-trunk 또는 clang-9.0.0으로 잘 컴파일됩니다 : https://godbolt.org/z/8GGT78

clang-trunk와의 오류 -std=c++2a:

<source>:12:19: error: use of overloaded operator '!=' is ambiguous (with operand types 'Foo' and 'Foo')
    Meta res = (f != g);
                ~ ^  ~
<source>:6:10: note: candidate function
    Meta operator!=(const Foo&) {return Meta{};}
         ^
<source>:5:10: note: candidate function
    Meta operator==(const Foo&) {return Meta{};}
         ^
<source>:5:10: note: candidate function (with reversed parameter order)

C ++ 20은 과부하 만 가능하게 operator==하고 컴파일러는 operator!=의 결과를 무시하여 자동으로 생성 한다는 것을 알고 있습니다 operator==. 내가 이해하는 한, 이것은 반환 유형이 인 한 작동 bool합니다.

문제의 원인은 고유치 우리 사업자들의 세트를 선언하는 것입니다 ==, !=, <사이 … Array물체 나 Array및 스칼라 창 (식의) 배열 bool다음 요소 와이즈 접속되거나, 그렇지 않으면 사용될 수있다 ( ). 예 :

#include <Eigen/Core>
int main()
{
  Eigen::ArrayXd a(10);
  a.setRandom();
  return (a != 0.0).any();
}

위의 예제와 달리 gcc-trunk에서도 실패합니다 : https://godbolt.org/z/RWktKs . clang-trunk와 gcc-trunk 모두에서 실패하는 고유하지 않은 예제로 이것을 줄이기 위해 아직 관리하지 않았습니다 (맨 위의 예제는 매우 간단합니다).

관련 문제 보고서 : https://gitlab.com/libeigen/eigen/issues/1833

내 실제 질문 : 이것은 실제로 C ++ 20의 주요 변경 사항입니까 (그리고 메타 연산자를 반환하기 위해 비교 연산자를 오버로드 할 가능성이 있습니까) 아니면 clang / gcc에서 회귀 가능성이 있습니까?



답변

고유 문제는 다음과 같이 감소합니다.

using Scalar = double;

template<class Derived>
struct Base {
    friend inline int operator==(const Scalar&, const Derived&) { return 1; }
    int operator!=(const Scalar&) const;
};

struct X : Base<X> {};

int main() {
    X{} != 0.0;
}

표현의 두 후보는

  1. 다시 쓴 후보 operator==(const Scalar&, const Derived&)
  2. Base<X>::operator!=(const Scalar&) const

[over.match.funcs / 4operator!=의 범위로 반입하지 않은 X부산물 사용 선언 , # 2의 내장 객체 파라미터의 타입이다 const Base<X>&. 결과적으로 # 1은 해당 인수에 대해 더 나은 암시 적 변환 순서를 갖습니다 (파생-대-변환보다는 정확한 일치). # 1을 선택하면 프로그램이 잘못 작성됩니다.

가능한 수정 사항 :

  • 추가 using Base::operator!=;하기 위해 Derived, 또는
  • (가) 변경 operator==테이크 할 const Base&대신을 const Derived&.

답변

예, 실제로 코드는 C ++ 20에서 중단됩니다.

이 표현식 Foo{} != Foo{}에는 C ++ 20에 세 개의 후보가 있습니다 (C ++ 17에는 하나만있는 경우).

Meta operator!=(Foo& /*this*/, const Foo&); // #1
Meta operator==(Foo& /*this*/, const Foo&); // #2
Meta operator==(const Foo&, Foo& /*this*/); // #3 - which is #2 reversed

이것은 [over.match.oper] /3.4 의 새로운 재 작성 후보 규칙에서 비롯된 것 입니다. 우리의 Foo주장은 그렇지 않기 때문에 모든 후보자들은 실행 가능 const합니다. 가장 적합한 후보를 찾으려면 순위 결정자를 통과해야합니다.

최상의 실행 기능에 대한 관련 규칙은 [over.match.best] / 2입니다 .

이러한 정의 감안할 때, 실행 가능한 기능은 F1다른 가능한 기능보다 더 나은 기능으로 정의 된 F2모든 인수를위한 경우 i, 보다 더 나쁜 변환 순서가 아닌 다음 ICSi(F1)ICSi(F2)

  • […이 예와 관련이없는 많은 경우 …] 또는 그렇지 않은 경우
  • F2는 다시 작성된 후보 ([over.match.oper])이며 F1은 그렇지 않습니다.
  • F1과 F2는 다시 작성된 후보이고, F2는 반대 순서의 매개 변수를 가진 합성 된 후보이며 F1은 그렇지 않습니다.

#2#3후보 재 기입하고, #3상태 파라미터의 순서를 역전하고있다 #1재기록되지 않는다. 그러나 그 계급 자에 도달하려면 먼저 그 초기 조건을 통과해야합니다. 모든 인수에 대해 변환 순서가 나쁘지 않습니다.

#1#2모든 변환 시퀀스가 ​​동일하고 (사소하게는 함수 매개 변수가 동일하기 때문에) #2재 작성 후보이지만 #1그렇지 않은 것보다 낫습니다 .

그러나 … 두 쌍 #1/ #3#2/ #3 는 그 첫 번째 조건에 붙어 있습니다. 두 경우 모두 첫 번째 매개 변수는 #1/에 대해 더 나은 변환 순서를 #2갖지만 두 번째 매개 변수는 더 나은 변환 순서를 갖습니다 (추가 검증 을 받아야하는 #3매개 변수 이므로 변환 순서가 더 나쁩니다). 이 플립 플롭은 우리가 둘 중 하나를 선호하지 못하게합니다.constconstconst

결과적으로 전체 과부하 해상도가 모호합니다.

내가 이해하는 한, 이것은 반환 유형이 인 한 작동 bool합니다.

맞지 않습니다. 우리는 무조건 재 작성 및 역 후보 후보자를 고려합니다. 우리가 가진 규칙은 [over.match.oper] / 9입니다 .

operator==운영자에 대해 과부하 해결을 통해 재 작성된 후보자를 선정 @하면 그 반환 유형은 cv가됩니다. bool

즉, 우리는 여전히 이러한 후보들을 고려합니다. 그러나 가장 실행 가능한 후보가 다음과 같은 결과 operator==를 반환 Meta하는 경우-결과는 기본적으로 해당 후보가 삭제 된 것과 동일합니다.

과부하 해결이 리턴 유형을 고려해야하는 상태 가 아니 었습니다 . 그리고 어쨌든 여기에서 코드가 반환한다는 사실 Meta은 중요하지 않습니다 bool. 반환 된 경우에도 문제가 발생합니다 .


고맙게도 여기의 수정은 쉽습니다.

struct Foo {
    Meta operator==(const Foo&) const;
    Meta operator!=(const Foo&) const;
    //                         ^^^^^^
};

두 개의 비교 연산자를 작성하면 const더 이상 모호성이 없습니다. 모든 매개 변수가 동일하므로 모든 변환 시퀀스가 ​​거의 동일합니다. #1이제는 #3다시 쓰지 않고 이길 것이고 , 뒤집 히지 않으면 서 #2이길 것 입니다. C ++ 17에서와 동일한 결과를 얻을 수있는 몇 가지 단계가 더 있습니다.#3#1


답변

[over.match.best] / 2는 세트의 유효한 과부하가 우선 순위를 매기는 방법을 나열합니다. 섹션 2.8 은 ( 다른 많은 것들 중에서) if F1보다 낫다 는 것을 알려줍니다 .F2

F2재 작성된 후보 ([over.match.oper])을하고 F1아니다

이 예제는 operator<비록 명시 적이라고 부르는 것을 보여줍니다 operator<=>.

그리고 [over.match.oper] /3.4.3operator== 은이 상황에서 후보자가 다시 작성된 후보 라고 알려줍니다 .

그러나 운영자는 한 가지 중요한 사실을 잊어 버립니다. 즉 const기능 이어야합니다 . 그리고 그것들을 만들지 않으면 const과부하 해결의 초기 측면이 작동 하지 않습니다 . 다른 인수에 대해 비- const대- const변환이 발생해야하기 때문에 두 함수 모두 정확히 일치하는 것은 아닙니다 . 그로 인해 문제가 모호해집니다.

당신이 그들을하게되면 const, 연타는 컴파일을 트렁크 .

코드를 모르기 때문에 나머지 Eigen과 대화 할 수 없으며 코드가 매우 커서 MCVE에 맞지 않습니다.


답변

Goopax 헤더 파일과 비슷한 문제가 있습니다. clang-10 및 -std = c ++ 2a로 다음을 컴파일하면 컴파일러 오류가 발생합니다.

template<typename T> class gpu_type;

using gpu_bool     = gpu_type<bool>;
using gpu_int      = gpu_type<int>;

template<typename T>
class gpu_type
{
  friend inline gpu_bool operator==(T a, const gpu_type& b);
  friend inline gpu_bool operator!=(T a, const gpu_type& b);
};

int main()
{
  gpu_int a;
  gpu_bool b = (a == 0);
}

이러한 추가 연산자를 제공하면 문제가 해결되는 것 같습니다.

template<typename T>
class gpu_type
{
  ...
  friend inline gpu_bool operator==(const gpu_type& b, T a);
  friend inline gpu_bool operator!=(const gpu_type& b, T a);
};


답변