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


11

다음 코드는 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에서 회귀 가능성이 있습니까?


답변:


5

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

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&.

실제 코드가를 반환 할 수없는 이유 거기에 bool자신의에서는 operator==? 그것이 새로운 규칙에 따라 코드가 잘못 작성된 유일한 이유 인 것 같습니다.
Nicol Bolas

4
실제 코드는 포함 operator==(Array, Scalar)요소 현명한 비교를 수행하고 돌아 그 Array의를 bool. bool다른 모든 것을 깨지 않고 는 그것을 바꿀 수 없습니다 .
TC

2
이것은 표준의 결함과 비슷합니다. 다시 쓰기 규칙은 operator==기존 코드에 영향을 미치지 않았지만이 경우에는 bool반환 값 확인 이 다시 쓰기 후보를 선택하는 것이 아니기 때문에 적용 됩니다.
Nicol Bolas

2
@NicolBolas : 준수해야 할 일반적인 원칙은 구현 변경이 다른 코드의 해석에 자동으로 영향을 미치지 않도록 해야것이 아니라 수행 할 있는 것이 아니라 무엇을 할 있는지 ( 예 : 연산자 호출) 여부를 확인하는 것 입니다. 재 작성된 비교는 많은 것들을 망가 뜨리지 만, 대부분 이미 의심스럽고 수정하기 쉬운 것들로 밝혀졌습니다. 따라서 더 좋거나 나쁘게도 이러한 규칙은 어쨌든 채택되었습니다.
Davis Herring

와우, 정말 고마워, 귀하의 솔루션으로 문제를 해결할 것으로 생각합니다 (현재 합리적인 노력으로 gcc / clang 트렁크를 설치할 시간이 없으므로 최신의 안정적인 컴파일러 버전으로 업그레이드되는지 확인하십시오. ).
chtz

11

예, 실제로 코드는 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


" 우리는 과부하 해결이 리턴 유형을 고려해야하는 상태에 있지 않기를 원했습니다. "오버로드 해결 자체는 리턴 유형을 고려하지 않지만 후속 재 작성 작업은 명확 합니다. 과부하 해결이 재 작성을 선택 ==하고 선택한 함수의 반환 유형이 아닌 경우 코드가 잘못 구성됩니다 bool. 그러나이 컬링은 과부하 해결 자체에는 발생하지 않습니다.
Nicol Bolas

반환 유형이 연산자를 지원하지 않는 경우에만 실제로 잘못 형성됩니다 ....
Chris Dodd

1
@ChrisDodd 아니요, 정확하게 cv bool변경 되어야합니다 (이 변경 전에 요구 사항은 상황에 맞는 상황으로 전환 bool되지 않았습니다 !)
Barry

불행히도 이것은 실제 문제를 해결하지 못하지만 실제로 문제를 설명하는 MRE를 제공하지 못했기 때문입니다. 나는 이것을 받아들이고 내 문제를 제대로 줄일 수있을 때 새로운 질문을 할 것이다 ...
chtz

2
원래 문제에 대한 적절한 축소 방법은 다음과 같습니다. gcc.godbolt.org/z/tFy4qz
TC

5

[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에 맞지 않습니다.


2
모든 인수에 대해 동일한 전환이있는 경우에만 순위를 매기십시오. 누락되지 않았기 때문에 const반전되지 않은 후보는 두 번째 인수에 대해 더 나은 변환 시퀀스를 가지며 반전 된 후보는 첫 번째 인수에 대해 더 나은 변환 시퀀스를 갖지 않습니다.
Richard Smith

@RichardSmith : 예, 그것은 제가 이야기 한 복잡한 종류였습니다. 그러나 나는 실제로 그 규칙들을 읽고 읽고 / 내장 화하고 싶지는 않았다;)
Nicol Bolas

실제로, 나는 const최소한의 예에서 잊어 버렸습니다 . 나는 Eigen이 const모든 곳에서 (또는 클래스 정의 외부, const참조를 사용하여) 사용한다고 확신 하지만 확인해야합니다. 나는 시간을 찾을 때 Eigen이 사용하는 전체 메커니즘을 최소한의 예로 분해하려고합니다.
chtz

-1

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);
};

1
이것은 미리 수행했을 때 유용한 것이 아니 었습니까? 그렇지 않으면 어떻게 a == 0컴파일했을 까요?
Nicol Bolas

이것은 실제로 비슷한 문제가 아닙니다. Nicol이 지적했듯이 이것은 C ++ 17에서 이미 컴파일되지 않았습니다. C ++ 20에서는 계속 다른 이유로 컴파일되지 않습니다.
Barry

나는 얘기를 깜빡 했네요 : 우리는 또한 회원 연산자를 제공 gpu_bool gpu_type<T>::operator==(T a) const;하고 gpu_bool gpu_type<T>::operator!=(T a) const;와 C ++ - (17)이 잘 작동합니다. 그러나 이제 clang-10 및 C ++-20에서는 더 이상 찾을 수 없으며 대신 컴파일러는 인수를 교체하여 자체 연산자를 생성하려고 시도하지만 반환 유형이 아니기 때문에 실패합니다 bool.
Ingo Josopait
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.