[C++20] Three-way comparison
Three-way comparison(3방향 비교?)는 C++20에 새롭게 등장한 연산자 표현식입니다.
기본적인 형태는 a <=> b 로 표현하고, 피연산자간의 관계를 비교하기 위해서 사용합니다.
(형태가 우주선과 비슷하여 spaceship operator로 주로 불리는듯 합니다.)
가장 간단한 사용은 아래와 같습니다.
(a <=> b) < 0 // a < b일 경우 true를 리턴합니다.
(a <=> b) > 0 // a > b일 경우 true를 리턴합니다.
(a <=> b) == 0 // a == b일 경우 true를 리턴합니다.
위 간단한 코드를 통해 사용법이 strcmp와 유사하다는 것을 느끼실 수 있습니다.
하지만 단순히 위와 같이 사용된다면 해당 연산자에 대한 필요성에 의구심이 들 수 있습니다. 왜냐하면 위의 경우는 Two-way comparison으로도 충분히 표현할 수 있기 때문입니다. 따라서 용도를 제대로 파악하려면, Three-way comparison가 나온 계기에 대해서 이해해볼 필요가 있습니다.
일반적으로 두 객체를 비교할 수 있다면 해당 객체는 Orderd Type이고, 이는 즉 객체간의 관계가 존재함을 나타냅니다.
그리고 Three-way comparison 연산자 등장하기 전에는 이러한 비교 연산의 결과로 항상 true, false만 존재했습니다.
하지만, 두 피연산자의 관계에는 true, false를 제외하고 더 많은 상태가 존재할 수 있습니다.
객체의 모든 멤버에 대해서 비교가 이루어질 경우 strong equality, strong ordering이라고 할 수 있습니다. 그리고 몇몇 객체에 대해서만 비교가 행해진다면 이는 weak equality, weak ordering이라고 합니다.
이렇게 비교에는 다양한 상태가 존재할 수 있습니다. 그리고 Three-way comparison는 이러한 상태를 표현하기 위해서 등장하였습니다.
또한 boiler-plate 코드를 상당히 줄일 수 있습니다. 아래 처럼 어떤 객체를 완전히 비교하기 위해서는 아래와 같이 비교 연산자에 대해서 모두 구현해줘여 할 것입니다.
하지만 C++20의 을 사용하게 된다면 하나의 <=> 연산자를 구현함으로서 위의 모든 코드를 생략할 수 있습니다.
또한 default 연산자를 제공해주기 때문에 구현체 없이 아래와 같이 한줄로도 나타낼 수 있습니다.
(Compiler가 자동으로 <=>연산자를 생성하지 않는 이유에 대해서는 뒤에서 이야기 해보겠습니다.)
그리고 이번엔 멤버별 비교를 수행하기 위해서 아래와 같은 구조체가 있습니다. 그런데 이전과 다르게 첫 번째 멤버 변수가 ByteString이라는 객체입니다.
C++20 이전에는 객체간 비교연산을 위해서 아래와 같이 표현할 수 있습니다.
그리고 Three-way comparison을 사용하면 아래와 같이 표현할 수 있습니다. 우선 comp != 0을 통해서 두 변수가 같은지 판단합니다. 그리고 만약 같지 않다면 그대로 strong_ordering객체 (cmp)를 리턴하고, 그렇지 않다면 그 다음 변수를 비교하는 방식으로 <=>을 구현해 볼 수 있습니다.
참고로 의 default 구현체도 위와 유사하게 구현되어 있습니다.
비정적 멤버들을 산술적으로 비교하면서 재귀적으로 <=>을 호출하게 되고 같지 않은 결과가 나오게 되면 조기에 중단합니다.
그리고 위와 같이 Three-way comparison을 정의했다고 해서, 객체를 비교하기 위해서 <=>을 사용할 필요가 없습니다.
오히려 가이드라인에서는 ==, <, >=, ... 등등 기존 비교연산자를 사용하고, 사용해야 한다면 synth_three_way와 같은 helper을 사용하기를 권장하고 있습니다.
comparison category types
- strong_ordering
a와 b가 완전히 같음을 의미합니다.
즉 a대신 b를 사용했을때 동일한 결과를 리턴합니다. (substitutability)
또한 비교할 수 없는 값을 허용하지 않습니다. → a < b, a == b 또는 a > b 중 정확히 하나는 true이어야 합니다.
- weak_ordering
weak_ordering값이 == 0 이라고 해서 a와 b가 같다고 볼 수 없습니다.
즉, a대신 b를 사용했을때 동일한 결과를 보장하지 않습니다.
또한 비교할 수 없는 값을 허용하지 않습니다. → a < b, a == b 또는 a > b 중 정확히 하나는 true이어야 합니다.
- partial_ordering
partial_ordering값이 == 0 이라고 해서 a와 b가 같다고 볼 수 없습니다.
즉, a대신 b를 사용했을때 동일한 결과를 보장하지 않습니다.
또한 이전 두 반환 타입과 다르게 비교할 수 없는 값
위에서 확인할 수 있듯이 단순히 값을 비교하는 것을 넘어서 현재 비교 수준이 어떻게 되는지 알 수 있습니다.
참고로, 가장 강한 비교 타입은 그 보다 약한 비교 타입으로 암시적 변환이 가능합니다.
예를 들어 strong_ordering은 weak_ordering이나 parital_ordering으로 암시적 변환이 가능합니다. 하지만 그 역은 불가능 합니다.
참고 비교 비교에 대해 수학적으로 접근하여 정리해 놓은 글이 있습니다. 이 글을 참고하면 C++에서 비교 연산자를 구현할 때 좀더 명확한 기준점을 세우는데 도움이 될거 같습니다.
Mathematics behind Comparison #1: Equality and Equivalence Relations (foonathan.net)
Mathematics behind Comparison #1: Equality and Equivalence Relations
This series explains the mathematical theory behind equivalence and ordering relations and gives an overview into the new three way comparison operator. This part it's all about equality.
www.foonathan.net
Reference
CppCon 2019: Jonathan Müller “Using C++20's Three-way Comparison <=>” - YouTube