デフォルト比較 (C++20 以降)
比較演算子関数を明示的にデフォルト指定することで、クラスに対応するデフォルト比較をコンパイラに生成させることができます。
目次 |
[編集] 定義
デフォルト比較演算子関数とは、以下のすべての条件を満たす、テンプレートではない比較演算子関数(すなわち <=>、==、!=、<、>、<=、または >=)のことです。
- あるクラス
Cの 非静的メンバまたはフレンドである。 C内、またはCが不完全型でないコンテキストで、デフォルト指定として定義されている。const C&型の2つのパラメータ、またはC型の2つのパラメータを持つ。この場合、暗黙のオブジェクトパラメータ(存在する場合)は最初のパラメータとして扱われる。
このような比較演算子関数は、クラス C のデフォルト比較演算子関数と呼ばれます。
struct X { bool operator==(const X&) const = default; // OK bool operator==(const X&) = default; // Error: the implicit object // parameter type is X& bool operator==(this X, X) = default; // OK }; struct Y { friend bool operator==(Y, Y) = default; // OK friend bool operator==(Y, const Y&) = default; // Error: different parameter types }; bool operator==(const Y&, const Y&) = default; // Error: not a friend of Y
比較演算子関数の暗黙の定義における名前探索およびアクセスチェックは、その関数本体と同等のコンテキストから実行されます。クラス内でデフォルト指定として現れる比較演算子関数の定義は、その関数の最初の宣言でなければなりません。
[編集] デフォルト比較順序
クラス C が与えられた場合、以下の要素を順に並べたサブオブジェクトリストが形成されます。
Cの直接基底クラスのサブオブジェクトを、宣言順に。Cの非静的データメンバを、宣言順に。
- いずれかのメンバサブオブジェクトが配列型の場合、その要素を添え字の昇順で展開します。展開は再帰的です。配列型の配列要素は、サブオブジェクトが配列型でなくなるまで再度展開されます。
あらゆる C 型のオブジェクト x について、以下の説明で
xの(展開された)サブオブジェクトリストにおけるサブオブジェクトの数を n とします。xの(展開された)サブオブジェクトリストにおける i 番目のサブオブジェクトを x_i とします。ここで x_i は、派生クラスから基底クラスへの変換、クラスメンバアクセス式、および配列添え字式を x に適用することで形成されます。
struct S {}; struct T : S { int arr[2][2]; } t; // The subobject list for “t” consists of the following 5 subobjects in order: // (S)t → t[0][0] → t[0][1] → t[1][0] → t[1][1]
[編集] 三方向比較
クラス型の operator<=> は、任意の戻り値型でデフォルト指定として定義できます。
[編集] 比較カテゴリ型
3つの比較カテゴリ型があります。
| 型 | 等価な値は... | 比較不可能な値は... |
|---|---|---|
| std::strong_ordering | 識別不能 | 許可されなくなった。 |
| std::weak_ordering | 識別可能 | 許可されなくなった。 |
| std::partial_ordering | 識別可能 | 許可 |
[編集] 合成三方向比較
同じ型のglvalue a と b の間の型 T の合成三方向比較は、次のように定義されます。
- a <=> b のオーバーロード解決が使用可能な候補をもたらし、それを
Tにstatic_castで明示的に変換できる場合、合成比較は static_cast<T>(a <=> b) となります。 - それ以外の場合で、以下のいずれかの条件が満たされる場合、合成比較は未定義となります。
- a <=> b のオーバーロード解決が、少なくとも1つの実行可能候補を見つける。
-
Tが比較カテゴリ型ではない。 - a == b のオーバーロード解決が、使用可能な候補をもたらさない。
- a < b のオーバーロード解決が、使用可能な候補をもたらさない。
- それ以外の場合で、
Tが std::strong_ordering の場合、合成比較は次のようになります。
a == b ? std::strong_ordering::equal : a < b ? std::strong_ordering::less : std::strong_ordering::greater
- それ以外の場合で、
Tが std::weak_ordering の場合、合成比較は次のようになります。
a == b ? std::weak_ordering::equivalent : a < b ? std::weak_ordering::less : std::weak_ordering::greater
- それ以外の場合(
Tが std::partial_ordering の場合)、合成比較は次のようになります。
a == b ? std::partial_ordering::equivalent : a < b ? std::partial_ordering::less : b < a ? std::partial_ordering::greater : std::partial_ordering::unordered
[編集] プレースホルダー戻り値型
クラス型 C のデフォルト指定された三方向比較演算子関数(operator<=>)の宣言された戻り値型が auto の場合、戻り値型は、C 型のオブジェクト x の対応するサブオブジェクト間の三方向比較の戻り値型から推導されます。
x の(展開された)サブオブジェクトリストの各サブオブジェクト x_i について
- x_i <=> x_i のオーバーロード解決を実行します。オーバーロード解決が使用可能な候補をもたらさない場合、デフォルト指定された operator<=> は削除として定義されます。
- x_i <=> x_i の型の cv-非修飾バージョンを
R_iとします。R_iが比較カテゴリ型でない場合、デフォルト指定された operator<=> は削除として定義されます。
デフォルト指定された operator<=> が削除として定義されていない場合、その戻り値型は std::common_comparison_category_t<R_1, R_2, ..., R_n> として推導されます。
[編集] 非プレースホルダー戻り値型
デフォルト指定された operator<=> の宣言された戻り値型が auto でない場合、プレースホルダー型(例: decltype(auto))を含めることはできません。
x の(展開された)サブオブジェクトリストに、宣言された戻り値型の x_i と x_i 間の合成三方向比較が未定義となるサブオブジェクト x_i が存在する場合、デフォルト指定された operator<=> は削除として定義されます。
[編集] 比較結果
デフォルト指定された operator<=> のパラメータを x および y とし、x および y の(展開された)サブオブジェクトリストの各サブオブジェクトをそれぞれ x_i および y_i とします。 x と y の間のデフォルト三方向比較は、対応するサブオブジェクト x_i と y_i を i の昇順で比較することによって実行されます。
R を(推導された、または宣言された)戻り値型とします。x_i と y_i の間の比較結果は、x_i と y_i の間の型 R における合成三方向比較の結果となります。
- x と y の間のデフォルト三方向比較中に、x_i と y_i の間のサブオブジェクトごとの比較が結果 v_i を生成し、v_i != 0 を bool に文脈変換すると true になる場合、戻り値は v_i のコピーとなります(残りのサブオブジェクトは比較されません)。
- それ以外の場合、戻り値は static_cast<R>(std::strong_ordering::equal) となります。
#include <compare> #include <iostream> #include <set> struct Point { int x; int y; auto operator<=>(const Point&) const = default; /* non-comparison functions */ }; int main() { Point pt1{1, 1}, pt2{1, 2}; std::set<Point> s; // OK s.insert(pt1); // OK // two-way comparison operator functions are not required to be explicitly defined: // operator== is implicitly declared (see below) // the overload resolutions of other candidates will select rewritten candidates std::cout << std::boolalpha << (pt1 == pt2) << ' ' // false << (pt1 != pt2) << ' ' // true << (pt1 < pt2) << ' ' // true << (pt1 <= pt2) << ' ' // true << (pt1 > pt2) << ' ' // false << (pt1 >= pt2) << ' '; // false }
[編集] 等価比較
[編集] 明示的宣言
クラス型の operator== は、戻り値型 bool でデフォルト指定として定義できます。
クラス C と C 型のオブジェクト x が与えられた場合、x の(展開された)サブオブジェクトリストに、x_i == x_i のオーバーロード解決が使用可能な候補をもたらさないサブオブジェクト x_i が存在する場合、デフォルト指定された operator== は削除として定義されます。
x および y をデフォルト指定された operator== のパラメータとし、x および y の(展開された)サブオブジェクトリストの各サブオブジェクトをそれぞれ x_i および y_i とします。 x と y の間のデフォルト等価比較は、対応するサブオブジェクト x_i と y_i を i の昇順で比較することによって実行されます。
x_i と y_i の間の比較結果は、x_i == y_i の結果となります。
- x と y の間のデフォルト等価比較中に、x_i と y_i の間のサブオブジェクトごとの比較が結果 v_i を生成し、v_i を bool に文脈変換すると false になる場合、戻り値は false となります(残りのサブオブジェクトは比較されません)。
- それ以外の場合、戻り値は true となります。
#include <iostream> struct Point { int x; int y; bool operator==(const Point&) const = default; /* non-comparison functions */ }; int main() { Point pt1{3, 5}, pt2{2, 5}; std::cout << std::boolalpha << (pt1 != pt2) << '\n' // true << (pt1 == pt1) << '\n'; // true struct [[maybe_unused]] { int x{}, y{}; } p, q; // if (p == q) {} // Error: operator== is not defined }
[編集] 暗黙的宣言
クラス C が operator== という名前のメンバまたはフレンドを明示的に宣言しない場合、デフォルト指定された operator<=> ごとに、 演算子関数が暗黙的に宣言されます。暗黙的に宣言された各 operator== は、対応するデフォルト指定された operator<=> と同じアクセス権、関数定義、およびクラススコープを持ちますが、以下の変更が加えられます。
- 宣言子識別子は operator== に置き換えられます。
- 戻り値型は bool に置き換えられます。
template<typename T> struct X { friend constexpr std::partial_ordering operator<=>(X, X) requires (sizeof(T) != 1) = default; // implicitly declares: friend constexpr bool operator==(X, X) // requires (sizeof(T) != 1) = default; [[nodiscard]] virtual std::strong_ordering operator<=>(const X&) const = default; // implicitly declares: [[nodiscard]] virtual bool // operator==(const X&) const = default; };
[編集] 二次比較
クラス型の二次比較演算子関数(!=、<、>、<=、または >=)は、戻り値型 bool でデフォルト指定として定義できます。
@ を5つの二次比較演算子のいずれかとし、パラメータ x および y を持つデフォルト指定された operator@ ごとに、それが削除として定義されるかどうかを判断するために、最大2回のオーバーロード解決が実行されます(デフォルト指定された operator@ は候補として考慮されません)。
- 最初のオーバーロード解決は x @ y に対して実行されます。オーバーロード解決が使用可能な候補をもたらさない場合、または選択された候補が書き換えられた候補でない場合、デフォルト指定された operator@ は削除として定義されます。この場合、2回目のオーバーロード解決はありません。
- 2回目のオーバーロード解決は、選択された x @ y の書き換えられた候補に対して実行されます。オーバーロード解決が使用可能な候補をもたらさない場合、デフォルト指定された operator@ は削除として定義されます。
x @ y を bool に暗黙変換できない場合、デフォルト指定された operator@ は削除として定義されます。
デフォルト指定された operator@ が削除として定義されていない場合、x @ y の値を返します。
struct HasNoRelational {}; struct C { friend HasNoRelational operator<=>(const C&, const C&); bool operator<(const C&) const = default; // OK, function is defaulted };
[編集] キーワード
[編集] 不具合報告
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 2539 | C++20 | 合成三方向比較は、明示的変換が利用できない場合でも static_cast を選択する |
この場合 static_cast を選択しない |
| CWG 2546 | C++20 | デフォルト指定された二次 operator@ は、x @ y のオーバーロード解決が 使用不可能な書き換えられた候補を選択した場合に、 削除として定義されなかった |
削除として定義される この場合に |
| CWG 2547 | C++20 | 非クラス型の比較演算子関数がデフォルト指定できるかどうか 曖昧であった |
デフォルト指定できない |
| CWG 2568 | C++20 | 比較演算子関数の暗黙の定義が メンバアクセス規則に違反する可能性がある |
アクセスチェックは、 関数本体と同等の コンテキストから実行される |