デフォルト比較(C++20以上)
クラスに対する一貫性のある関係演算子の生成をコンパイラに要求する方法を提供します。
簡単に言うと、 operator<=> を定義するクラスは、コンパイラ生成された演算子 ==、 !=、 <、 <=、 >、および >= を自動的に得ます。 operator<=> はデフォルト化されたものとして定義することができ、その場合コンパイラはその演算子に対するコードも生成します。
class Point { int x; int y; public: auto operator<=>(const Point&) const = default; // ... その他の無関係な関数 ... }; // コンパイラは6つの関係演算子すべてを生成します。 Point pt1, pt2; if (pt1 == pt2) { /*...*/ } // OK。 std::set<Point> s; // OK。 s.insert(pt1); // OK。 if (pt1 <= pt2) { /*...*/ } // OK、 <=> を1回だけ呼びます。
目次 |
[編集] カスタム比較
デフォルトのセマンティクスが適切でないとき、例えばメンバを異なる順序で比較しなければならないときや、自然な比較と異なる比較を用いなければならないときなど、プログラマは operator<=> を書いて、適切な関係演算子をコンパイラに生成させることができます。 生成さ���る関係演算子の種類は、ユーザ定義された operator<=> の戻り値の型によります。
使用可能な戻り値の型は5つあります。
戻り値の型 | 演算子 | 同等な値は... | 比較不可能な値は... |
---|---|---|---|
std::strong_ordering | == != < > <= >= | 区別できません | 使用できません |
std::weak_ordering | == != < > <= >= | 区別できます | 使用できません |
std::partial_ordering | == != < > <= >= | 区別できます | 使用できます |
std::strong_equality | == != | 区別できません | 使用できません |
std::weak_equality | == != | 区別できます | 使用できません |
[編集] 強い順序
std::strong_ordering を返すカスタム operator<=> の例は、デフォルトと異なる順序でクラスのすべてのメンバを比較する演算子です。
class TotallyOrdered : Base { std::string tax_id; std::string first_name; std::string last_name; public: // last_name を最初に比較したいカスタム operator<=>。 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; } // ... その他の無関係な関数 ... }; // コンパイラは6つの関係演算子すべてを生成します。 TotallyOrdered to1, to2; if (to1 == to2) { /*...*/ } // OK。 std::set<TotallyOrdered> s; // OK。 s.insert(to1); // ok if (to1 <= to2) { /*...*/ } // OK、 <=> を1回呼びます。
ノート: std::strong_ordering を返す演算子はすべてのメンバを比較するべきです。 除外されるメンバがあると、代用可能性が損なわれます (等しいと比較される2つの値を区別することができるようになります)。
[編集] 弱い順序
std::weak_ordering を返すカスタム operator<=> の例は、大文字小文字区別なしでクラスの文字列メンバを比較する演算子です。 これはデフォルト比較と異なり (そのためカスタム演算子が必要です)、この比較の下で等しいと比較された2つの文字列は区別することができます。
class CaseInsensitiveString { std::string s; public: std::weak_ordering operator<=>(const CaseInsensitiveString& b) const { return case_insensitive_compare(s.c_str(), b.s.c_str()); } std::weak_ordering operator<=>(const char* b) const { return case_insensitive_compare(s.c_str(), b); } // ... その他の無関係な関数 ... }; // コンパイラは6つの関係演算子すべてを生成します。 CaseInsensitiveString cis1, cis2; if (cis1 == cis2) { /*...*/ } // OK。 set<CaseInsensitiveString> s; // OK。 s.insert(/*...*/); // OK。 if (cis1 <= cis2) { /*...*/ } // OK、比較演算を1回行います。 // コンパイラは12個の異種混合関係演算子も生成します。 if (cis1 <= "xyzzy") { /*...*/ } // OK、比較演算を1回行います。 if ("xyzzy" >= cis1) { /*...*/ } // OK、上と同じセマンティクス。
この例は異種混合 operator<=> が持つ効果をデモンストレーションしていることに注意してください。 両方向の異種混合比較が生成されます。
[編集] 半順序
半順序は、浮動小数点比較における NaN 値のような、比較不可能な (順序付けできない) 値を許容する順序です。
class PersonInFamilyTree { public: std::partial_ordering operator<=>(const PersonInFamilyTree& that) const { if (this->is_the_same_person_as ( that)) return partial_ordering::equivalent; if (this->is_transitive_child_of( that)) return partial_ordering::less; if (that. is_transitive_child_of(*this)) return partial_ordering::greater; return partial_ordering::unordered; } // ... その他の無関係な関数 ... }; // コンパイラは6つの関係演算子すべてを生成します。 PersonInFamilyTree per1, per2; if (per1 == per2) { /*...*/ } // OK、 per1 が per2 である場合。 else if (per1 < per2) { /*...*/ } // OK、 per2 が per1 の祖先である場合。 else if (per1 > per2) { /*...*/ } // OK、 per1 が per2 の祖先である場合。 else { /*...*/ } // per1 と per2 が無関係の場合。 if (per1 <= per2) { /*...*/ } // OK、 per2 が per1 であるか per1 の祖先である場合。 if (per1 >= per2) { /*...*/ } // OK、 per1 が per2 であるか per2 の祖先である場合。 if (per1 != per2) { /*...*/ } // OK、 per1 が per2 でない場合。
[編集] 強い等しさ
等しさには意味があるけれども大小関係には意味がない型はたくさんあります。 一般的な例は、複素数や、汎用の数値の組などです。
class EqualityComparable { std::string name; BigInt number1; BigInt number2; public: std::strong_equality operator<=>(const EqualityComparable& that) const { if (auto cmp = number1 <=> that.number1; cmp != 0) return cmp; if (auto cmp = number2 <=> that.number2; cmp != 0) return cmp; return name <=> that.name; } }; // コンパイラは == および != は生成しますが、 <、 >、 <=、および >= は生成しません。 EqualityComparable ec1, ec2; if (ec1 != ec2) { /*...*/ } // OK。
[編集] 弱い等しさ
この例では、この比較 (メンバ name
に対する大文字小文字区別なしの比較) で等しいと比較される2つの値は、大文字小文字区別ありの関数によって区別できます。
class EquivalenceComparable { CaseInsensitiveString name; BigInt number1; BigInt number2; public: std::weak_equality operator<=>(const EquivalenceComparable& that) const { if (auto cmp = number1 <=> that.number1; cmp != 0) return cmp; if (auto cmp = number2 <=> that.number2; cmp != 0) return cmp; return name <=> that.name; } // ... その他の無関係な関数 ... }; // コンパイラは != および == は生成しますが、 <、 >、 <=、および >= は生成しません。 EquivalenceComparable ec1, ec2; if (ec1 != ec2) { /*...*/ } // OK。
[編集] デフォルト化された三方比較
デフォルトの operator<=> は、 <=> を計算するために T の基底 (左から右、深さ優先)、そして非静的メンバ (宣言順) 部分オブジェクトを連続的に比較することによって、辞書的な比較を行います。 配列のメンバを再帰的に展開し (添字が増える順で)、等しくない結果が見つかると早期に停止します。 つまり、
for /* T のそれぞれの基底およびメンバ部分オブジェクト o */ if (auto cmp = lhs.o <=> rhs.o; cmp != 0) return cmp; return strong_ordering::equal; // すべてに変換します。
仮想基底部分オブジェクトが複数回比較されるかどうかは未規定です。
宣言された戻り値の型が auto の場合、実際の戻り値の型は std::common_comparison_category_t<Ms> です。 ただし Ms は比較される基底およびメンバ部分オブジェクトおよびメンバ配列要素の型のリストです (空の場合もあります)。 これは、以下のような、戻り値の型が自明でなくメンバに依存するケースを書くことを容易にします。
template<class T1, class T2> struct P { T1 x1; T2 x2; friend auto operator<=>(const P&, const P&) = default; };
そうでなければ、戻り値の型は5つの比較型 (上を参照してください) のいずれかでなければならず、いずれかの基底またはメンバ部分オブジェクトまたはメンバ配列要素に対する式 m1 <=> m2 が、選択された戻り値の型に暗黙に変換できない場合は、 ill-formed です。
いずれかの基底またはメンバ部分オブジェクトが、 std:: の比較カテゴリ型のいずれかを結果とするコンパイラ生成されたまたはそのスコープ内で (すなわち非静的メンバまたはフレンドとして) ユーザ宣言された operator<=> を持たない場合、デフォルト化された operaetor<=> は暗黙に削除され、 void を返します。
[編集] デフォルト化された二方比較
6つの二方関係演算子はいずれも明示的にデフォルト化できます。 デフォルト化された関係演算子は、戻り値の型が bool でなければなりません。
そのような演算子は、 x <=> y に対するオーバーロード解決 (引数が逆順の operator<=> も考慮されます) が失敗した場合、またはその operator@ がその x<=>y の結果に適用できない場合は、削除されます。 そうでなければ、デフォルト化された operator@ は、オーバーロード解決によって元の順序の引数を持つ operator<=> が選択された場合は x <=> y @ 0 を、そうでなければ 0 @ y <=> x を呼びます。
struct C { friend std::strong_equality operator<=>(const C&, const C&); friend bool operator==(const C& x, const C& y) = default; // OK、 x <=> y == 0 を返します。 bool operator<(const C&) = default; // OK、関数は削除されます。 };
関係演算子のデフォルト化は、アドレスが取られるかもしれない関数を作成するために便利な場合があります。 それ以外の用途については、 operator<=> を提供するだけで十分です。
[編集] 関連項目
- オーバーロード演算子の呼び出しにおけるオーバーロード解決
- 組み込みの三方比較演算子
- 関係演算子に対する演算子オーバーロード