【C++】一貫比較 – 三方比較演算子(<=>)
C++20にて一貫比較と呼ばれる機能が追加されました。これを行う演算子が三方比較演算子(<=>)です。これを使うと比較演算子の実装が簡単になります。本エントリでは、この三方比較演算子について調べてみたのでまとめてみたいと思います。
三方比較演算子とは
C++20で追加された新しい二項演算子です。その見た目から宇宙船演算子とも呼ばれます。この演算子を使うと、最大6種類の比較演算子(<
, <=
, >
, >=
, ==
, !=
)を実装するのが容易となります。
動作
ある値a
, b
に対する三方比較演算子の結果は、「より大きい」「より小さい」「等しい」の3つの関係を同時に表す値を返します。これは比較カテゴリ型と呼ばれる型であり、単純なbool
ではありません。
bool
値にするためには、戻り値と0を比較する必要があります。なお、比較カテゴリ型のクラスは<compare>
で定義されているので、使う場合にはインクルードしておきます。
#include <compare>
#include <iostream>
int a = 0, b = 1;
auto result = a <=> b;
if(result < 0){
std::cout << "a < b" << std::endl;
}
else if(result > 0){
std::cout << "a > b" << std::endl;
}
else if(result == 0){
std::cout << "a == b" << std::endl;
}
独自クラスに対する三方比較演算子の定義
独自に作成したクラスに対して三方比較演算子を定義すると、比較演算子<
, <=
, >
, >=
が自動的に生成されます。また、三方比較演算子をdefault
定義した場合、(すでに==
が定義されていなければ)==
もdefault
定義され、!=
は==
から自動生成されるため、最大6種類の比較演算子が自動的に生成されることとなります。
これにより、比較演算子の定義が容易となり、実装の手間が少なくなります。
#include <iostream>
class C
{
public:
auto operator<=>(const C &) const = default;
int a;
char b;
};
int main()
{
C c1{0, 'A'}, c2{1, 'B'}, c3{0, 'B'};
std::cout << std::boolalpha;
std::cout << (c1 < c2) << std::endl; // true
std::cout << (c1 == c2) << std::endl; // false
std::cout << (c1 < c3) << std::endl; // true
return 0;
}
default
定義した場合は、メンバ同士を順に比較します。
三方比較演算子をdefault
定義しなかった場合、==
は自動生成されないため、==
も一緒に定義する必要があります。
比較カテゴリ型
三方比較演算子を独自に定義する場合、比較カテゴリ型のオブジェクトを戻り値とする必要があります。比較カテゴリ型のクラスは3種類あり、std::partial_ordering
, std::weak_ordering
, std::strong_ordering
です。
それぞれ以下のような違いがあります。
std::partial_ordering
半順序。順序付け不能な値が存在する。std::weak_ordering
弱順序。順序付け不能な値を同値として取り扱い比較可能とする。std::strong_ordering
全順序。順序付け不能な要素がない。
三方比較演算子の定義例
独自に作成したクラスに三方比較演算子を定義した例を示します。Month
クラスは月を表すクラスで、Month::MonthId::January
からMonth::MonthId::December
までの12個の値を内部的に取り得ます。このクラスに対し、Month
クラスのオブジェクト同士、Month::MonthId
との比較、符号なし整数との比較の3種類を実装してみました。
#include <compare>
#include <iostream>
class Month
{
public:
enum class MonthId : unsigned int {
January = 1, Febrary, March, April, May, June,
July, August, September, October, November, December
};
Month() = delete;
explicit Month(MonthId id) : m(id) {}
auto operator<=>(const Month &) const = default;
bool operator==(const Month &) const = default; /* 明示的に定義する必要あり */
std::strong_ordering operator<=>(const MonthId id) const
{
return m <=> id;
}
bool operator==(const MonthId id) const
{
return m == id;
}
std::partial_ordering operator<=>(const unsigned int v) const
{
if(0 < v && v < 13){
return static_cast<unsigned int>(m) <=> v;
}
return std::partial_ordering::unordered;
}
bool operator==(const unsigned int v) const
{
return static_cast<unsigned int>(m) == v;
}
private:
MonthId m;
};
int main()
{
Month a(Month::MonthId::January);
Month b(Month::MonthId::August);
std::cout << std::boolalpha;
std::cout << (a < b) << std::endl;
std::cout << (a == b) << std::endl;
std::cout << (a > b) << std::endl << std::endl;
std::cout << (a < Month::MonthId::January) << std::endl;
std::cout << (a == Month::MonthId::January) << std::endl;
std::cout << (a > Month::MonthId::January) << std::endl << std::endl;
std::cout << (b < 6) << std::endl;
std::cout << (b == 6) << std::endl;
std::cout << (b > 6) << std::endl << std::endl;
std::cout << (b < 13) << std::endl;
std::cout << (b == 13) << std::endl;
std::cout << (b > 13) << std::endl;
return 0;
}
出力結果
true
false
false
false
true
false
false
false
true
false
false
false
Month
クラスのオブジェクト同士の比較では、メンバ同士の比較となるため、default
実装で定義可能です。ただし==
を定義しているため、==
は自動的にはdefault
実装されません。明示的にdefault
実装する必要があります。
Month::MonthId
との比較や符号なし整数との比較では、三方比較演算子を定義しているため、同時に==
も定義する必要があります。Month
クラスのオブジェクトは、Month::MonthId::January
からMonth::MonthId::December
までの値しか取り得ないため、Month::MonthId
との三方比較では全順序としてstd::strong_ordering
型のオブジェクトを返すようにしました。
一方、符号なし整数との三方比較では、0や13といった月として存在しない数値が指定される可能性があるため、これらの数値が指定された場合に順序付け不能として、std::partial_ordering::unordered
を返すようにしました。三方比較演算子がstd::partial_ordering::unordered
を返した場合、比較演算子の結果はfalse
となります。
まとめ
C++20にて追加された新しい二項演算子である、三方比較演算子について調べてみました。まとめると以下になります。
bool
型ではなく、比較カテゴリ型を返す演算子- 三方比較演算子を
default
実装すると、<
,<=
,>
,>=
,==
,!=
の最大6種の比較演算子を自動生成する - 三方比較演算子を独自実装する場合、
==
も実装する必要がある - 比較カテゴリ型は以下の3種類
std::partial_ordering
半順序。順序付け不能な値が存在する。std::weak_ordering
弱順序。順序付け不能な値を同値として取り扱い比較可能とする。std::strong_ordering
全順序。順序付け不能な要素がない。
ディスカッション
コメント一覧
まだ、コメントがありません