Default comparisons (since 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; // エラー: 暗黙のオブジェクト // パラメータ型は X& bool operator==(this X, X) = default; // OK }; struct Y { friend bool operator==(Y, Y) = default; // OK friend bool operator==(Y, const Y&) = default; // エラー: 異なるパラメータ型 }; bool operator==(const Y&, const Y&) = default; // エラー: Yのフレンドではない
比較演算子関数の暗黙的な定義における名前探索とアクセスチェックは、その関数本体と同等のコンテキストから実行されます。クラス内でデフォルト化された比較演算子関数の定義は、その関数の最初の宣言でなければなりません。
デフォルト比較順序
クラス
C
が与えられたとき、サブオブジェクトリストは以下のサブオブジェクトによって順に形成されます:
-
Cの直接基底クラスの部分オブジェクト(宣言順) -
Cの非静的 data members (宣言順)
-
- いずれかのメンバー部分オブジェクトが配列型である場合、添字の昇順でその要素のシーケンスに展開されます。この展開は再帰的です:配列型の配列要素は、配列型の部分オブジェクトがなくなるまで再度展開されます。
型
C
の任意のオブジェクト
x
について、以下の説明において:
- n を x の(展開された)サブオブジェクトリスト内のサブオブジェクト数とする。
- x_i を x の(展開された)サブオブジェクトリスト内の i 番目のサブオブジェクトとする。ここで x_i は、 x に適用される 派生クラスから基底クラスへの変換 、 クラスメンバアクセス式 、および 配列添字式 のシーケンスによって形成される。
struct S {}; struct T : S { int arr[2][2]; } t; // 「t」の部分オブジェクトリストは、以下の5つの部分オブジェクトを順に含む: // (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 | 区別できる | 許可される |
合成三項比較
合成された三方比較
は、同じ型の
T
型の左辺値
a
と
b
の間で以下のように定義されます:
-
a <=> bのオーバーロード解決が使用可能な候補を生成し、static_castを用いて明示的にTへ変換可能な場合、合成された比較は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_i が存在し、宣言された戻り値型の 合成三方向比較 が x_i と x_i の間で定義されていない場合、デフォルト化された operator <=> は削除済みとして定義されます。
比較結果
x と y をデフォルト化された operator <=> のパラメータとし、 x と y の(展開された)サブオブジェクトリスト内の各サブオブジェクトをそれぞれ x_i と y_i と表記する。 x と y の間のデフォルトの三方比較は、対応するサブオブジェクト x_i と y_i を i の昇順で比較することによって実行される。
R
を(推論される可能性のある)戻り値の型とし、
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; /* 比較以外の関数 */ }; int main() { Point pt1{1, 1}, pt2{1, 2}; std::set<Point> s; // OK s.insert(pt1); // OK // 双方向比較演算子関数を明示的に定義する必要はありません: // operator== は暗黙的に宣言されます(下記参照) // 他の候補のオーバーロード解決では書き換えられた候補が選択されます 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 を文脈上 bool に変換した結果が false となる場合、戻り値は false となる(残りの部分オブジェクトは比較されない)。
- それ以外の場合、戻り値は true となる。
#include <iostream> struct Point { int x; int y; bool operator==(const Point&) const = default; /* 比較以外の関数 */ }; 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) {} // エラー: operator== が定義されていません }
暗黙の宣言
クラス
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; // 暗黙的に宣言: friend constexpr bool operator==(X, X) // requires (sizeof(T) != 1) = default; [[nodiscard]] virtual std::strong_ordering operator<=>(const X&) const = default; // 暗黙的に宣言: [[nodiscard]] virtual bool // operator==(const X&) const = default; };
セカンダリ比較
クラス型に対する二次比較演算子関数(
!=
、
<
、
>
、
<=
または
>=
)は、戻り値型
bool
でデフォルト定義することができます。
@
を5つの二次比較演算子の1つとし、各デフォルト化された
operator@
について、パラメータ
x
と
y
を持つ場合、最大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、関数はデフォルト化されている };
キーワード
不具合報告
以下の動作変更の欠陥報告書は、以前に公開された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 |
比較演算子関数の暗黙的な定義が
メンバーアクセス規則に違反する可能性があった |
アクセスチェックは
それらの関数本体と同等の コンテキストから実行される |
関連項目
- オーバーロード解決 オーバーロードされた演算子の呼び出しにおける
- 組み込みの three-way comparison operator
- Operator overloading 比較演算子の