다음 코드는 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;
}
표현의 두 후보는
- 다시 쓴 후보
operator==(const Scalar&, const Derived&)
Base<X>::operator!=(const Scalar&) const
당 [over.match.funcs / 4 등 operator!=
의 범위로 반입하지 않은 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
매개 변수 이므로 변환 순서가 더 나쁩니다). 이 플립 플롭은 우리가 둘 중 하나를 선호하지 못하게합니다.const
const
const
결과적으로 전체 과부하 해상도가 모호합니다.
내가 이해하는 한, 이것은 반환 유형이 인 한 작동
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);
};