0%

How to write comparsion operator for custom type in C++

How to write comparsion operator for custom type

The simple case

假設我們有一個類別

1
2
3
struct Value {
int v;
};

我們要怎麼寫出的程式碼

1
2
Value v1, v2;
v1 < v2;

有幾種方式

Naive solution

一種是當member function存在
手動寫出所有comparsion operator

1
2
3
4
5
6
struct Value {
int v;
bool operator<(const Value &rhs) { return v < rhs.v; }
bool operator==(const Value &rhs) { return v == rhs.v; }
// Ignore
};

另外一種是Free function存在

1
2
bool operator<(const Value &lhs, const Value &rhs) { return lhs.v < rhs.v; }
bool operator==(const Value &lhs, const Value &rhs) { return lhs.v == rhs.v; }

兩種實現原理相同,看情況選擇要用哪種,現在要討論的是其他的問題
當我們需要支持更多運算符號時,我們就需要寫更多的Function

1
2
3
bool operator>(const Value &lhs, const Value &rhs);
bool operator==(const Value &lhs, const Value &rhs);
bool operator!=(const Value &lhs, const Value &rhs);

如果我們需要支援另外一種Type

1
2
3
4
struct Value1 {
int v;
int v1;
};

然後又要出現一堆複製貼上加上手動修改的產物

1
2
3
4
bool operator<(const Value1 &lhs, const Value1 &rhs);
bool operator>(const Value1 &lhs, const Value1 &rhs);
bool operator==(const Value1 &lhs, const Value1 &rhs);
bool operator!=(const Value1 &lhs, const Value1 &rhs);

寫起來麻煩又沒什麼技術含量

CRTP solution

有些operator可以用其他operator表示,例如Not Equal就是Not + Equal
所以我們可以用CRTP技巧減少我們的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<class Derived>
struct Equality {
bool operator !=(const Equality &rhs) {
return !(static_cast<Derived&>(*this) == static_cast<const Derived&>(rhs));
}
};

struct Value : Equality<Value> {
int v;
bool operator==(const Value &rhs) const { return v == rhs.v; }
};

struct Value1 : Equality<Value1> {
int v;
int v1;
bool operator==(const Value1 &rhs) const { return v == rhs.v; && v1 == rhs.v1; }
};

其他的operator可以如法炮製,很多的C++ Graphics/Math Library都用了這個技巧
只要實作<==,可以用來推導出其他四種比較關係
不過很不直觀,CRTP就是一種Hack,那有沒有更好的方法

C++20 spaceship operator

Spaceship oerator也叫做The Three-Way Comparison Operator
這是C++20的一個特性,直接上Code來說明

1
2
3
4
5
#include <compare>
struct Value {
        int v;
        auto operator<=>(const Value&) const = default; (1)
};

而Compiler直接為你生成Comparsion Code,原先的程式碼視為這樣

1
2
3
(a <=> b) < 0  //true if a < b
(a <=> b) > 0 //true if a > b
(a <=> b) == 0 //true if a is equal/equivalent to b

這種方式類似於strcmp,會回傳<0>00三種情形
基本上這樣就滿足了80%的需求了,不過人生最難的就是那個But
有需要的話自定義比較方式的話,可以自定義comparsion operator

1
2
3
4
5
6
7
8
9
10
11
struct Value1 {
int v;
int v1;
public:
auto operator<=>(const Value1& rhs) const {
   if (auto cmp = v <=> rhs.v; cmp != 0)
   return cmp;
return v1 <=> rhs.v1;
}
 }
};

不過現在spaceship operator必須回傳的是std::strong_orderingstd::weak_orderingstd::partial_ordering其中之一
至於三種ordering的差異,在此不探討,需要的話去Reference看,大部分只需要std::strong_ordering即能完成需求

Reference