C++20 的三向比較(Three-way comparison)

| | 0 Comments| 10:19
Categories:

三向比較(three-way comparison、參考)是 C++20 新增的一項新的運算子,他的形式是「<=>」;據說是由於外型的關係,所以也被稱為「Spaceship Operator」。

而它的特色呢,則是可以針對兩個變數進行比較,並透過一個回傳值讓使用者可以判斷到底是大於、小於、還是等於;基本上是:

  • a < b 的話:(a <=> b) < 0
  • a > b 的話:(a <=> b) > 0
  • a 和 b 相等或等價的話:(a <=> b) == 0

而實際上,根據變數的型別的不同,他可能會回傳不同型別的結果來做區隔。

<compare> 這個新的 header 檔(文件)裡面,就還有定義出三種不同的比較結果:

  • std::strong_ordering
    • 比較結果有 lessgreaterequalequivalent,後兩者基本上相同
    • 整數型別的比較結果就是這種
  • std::weak_ordering
    • 比較結果有  lessgreaterequivalent
    • std::strong_ordering
      的差別是只有 equivalent
      而沒有 equal
  • std::partial_ordering
    • 比較結果有  lessgreaterequivalentunordered
    • 多了無法比較的狀況,也就是 unordered
    • 浮點數的比較結果會是這種,如果其中有一個是 NaN 就會變成 unordered

而雖然這三種型別的結果都可以用 == 0> 0< 0 來做判斷,不過 <compare> 裡也有提供命名的函式、來方便使用;包括了 is_eq()is_neq()is_lt()is_lteq()is_gt()is_gteq(),可以試需要使用。

下面就是一個簡單的使用例子:

auto res = (1.0f <=> 1.0f);
if (std::is_eq(res))
  std::cout << "equal\n";
else
  std::cout << "not equal\n";

另外,按照強度的關係,也可以把較強的比較結果轉換成較弱的結果,其關係就是:

strong_ordering -> weak_ordering -> partial_ordering

反過來則無法直接轉換;下面是個簡單的例子:

std::strong_ordering so = std::strong_ordering::equal;
std::weak_ordering wo = so;
std::partial_ordering po = so;
std::weak_ordering wo1 = po; //ERROR

而在 Heresy 來看,定義三向比較的函式比較大的好處,在於如果針對自己定義的類別定義了 operator<=> 後,編譯器就可以自動產生其他的比較函式、讓這個類別可以做任意的比較了!

它會自動產生的比較函式包括了:==!=<<=>>= 這六個。

在最簡單的狀況下,只要加入一行、告訴編譯器要使用預設的三向比較函式就可以了;下面就是一個例子:

struct SData
{
  int iVal = 0;
 
  auto operator<=>(const SData&) const = default;
};

如此一來,SData 這個型別的資料,就可以在同型別的資料之間做各種比較,而不需要自己去各自定義不同的比較函式了。

預設的 operator<=> 會進行字典式的比較(lexicographical comparison),順序是先比較 base class(由左而右、深度優先)、然後再按照宣告順序來比較 non-static 的成員資料;而針對陣列,他應該也是會去依序比較內容。

這些比較會依序進行、在遇到不相等的時候就會停下來、回傳比較的結果;所以在一般狀況下,預設的比較方式應該算是可以直接拿來用的。

而如果預設的比較模式不符合需求的話,也可以自己定義比較的方法。

在《Default comparisons》,也提供了一些自訂比較方法的例子。

像是如果只是想要修改比較順序的話,可以寫成:

struct Base {
  std::string zip;
  auto operator<=>(const Base&) const = default;
};
 
struct TotallyOrdered : Base {
  std::string tax_id;
  std::string first_name;
  std::string last_name;
 
public:
  // custom operator<=> because we want to compare last names first:
  std::strong_ordering operator<=>(const TotallyOrdered& that) const {
    if (auto cmp = (Base&)(*this) <=> (Base&)that; cmp != 0)
      return cmp;
    if (auto cmp = last_name <=> that.last_name; cmp != 0)
      return cmp;
    if (auto cmp = first_name <=> that.first_name; cmp != 0)
      return cmp;
    return tax_id <=> that.tax_id;
  }
  // ... non-comparison functions ...
};

他會先去用預設的方法比較基礎型別 Base,然後在按照 last_namefirst_nametax_id 的順序來比較資料。

而如果有需要撰寫進一步的比較方法,也是可以透過自己定義 operator<=> 的內容來達成的~


參考:

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *