多くのC ++標準ライブラリコードで不等式が(!(a == b))としてテストされるのはなぜですか?


142

これは、C ++標準ライブラリremoveコードのコードです。if (!(*first == val))ではなく、なぜ不等式がテストされるのif (*first != val)ですか?

 template <class ForwardIterator, class T>
      ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val)
 {
     ForwardIterator result = first;
     while (first!=last) {
         if (!(*first == val)) {
             *result = *first;
             ++result;
         }
         ++first;
     }
     return result;
 }

2
@BeyelerStudiosはおそらく正しいです。を実装する場合も一般的operator!=です。operator==実装を使用してください:bool operator!=(const Foo& other) { return !(*this == other); }
simon

1
実際に私は私の文を修正:参照が言及削除しに、すべての要素に等しい値をのでoperator==...種類、ここで使用されることが期待である
BeyelerStudios

ああ、そしてconst私の前のコメントの例にもあるはずですが、あなたは要点を理解します。(編集するには遅すぎます)
サイモン、2015

この理由は、別の質問(基本的には「いいえ、必ずしもそうではない」で答えることができます)と、EqualityComparableHurkylが彼の回答で述べたという概念に関連しています
Marco13 2018年

回答:


144

これは、Tに対する唯一の要件がの実装であることを意味するためoperator==です。Tにを要求することもできますoperator!=が、ここでの一般的な考え方は、テンプレートのユーザーにできる限り負担をかけず、他のテンプレートが必要とすることですoperator==


13
テンプレート<クラスT>インラインブール演算子!= <T a、T b> {return!(a == b); }
ジョシュア

8
コンパイラが=!のすべてのインスタンスを交換できないシナリオはありますか?!(==)?これがコンパイラのデフォルトのアクションではないのはなぜですか?
Aidan Gomez

20
@AidanGomez良くも悪くも、演算子をオーバーロードして好きなことを実行できます。理にかなっている必要も、一貫している必要もありません。
Neil Kirk、

10
x != yと同じになるように定義されていません!(x == y)。これらのオペレーターが埋め込みDSLの解析ツリーを返すとどうなりますか?
Brice M. Dempsey 2015

7
@Joshua SFINAEを使用してサポートされているかどうかを検出しようとすると、うまく動作しません(サポートされていなく!=ても、誤ってtrueを返しますoperator==!)。また、一部の用途が!=不明確になることも心配です。

36

STLのほとんどの関数は、operator<またはでのみ機能しoperator==ます。これにより、ユーザーはこれら2つの演算子(または、少なくとも1つの演算子)を実装するだけで済みます。例えばstd::set用途operator<(より正確にはstd::less呼び出したoperator<デフォルトでは)ないoperator>順序を管理します。removeあなたの例のテンプレートは同様のケースです-それは使用するだけで使用operator==しないoperator!=ので、をoperator!=定義する必要はありません。


2
関数はoperator<直接使用せず、代わりにを使用std::lessoperator<ます。
クリスチャンハックル2015

1
実際、例えばstd::set、とは異なり、標準のアルゴリズム関数は実際にoperator<直接使用しているようです。奇妙な...
クリスチャンハックル2015

1
これらの関数はを使用せず、質問で述べたようにstd::equal_to使用operator==します。との状況std::lessは似ています。まあ、多分std::setそれは最良の例ではありません。
ルカシュBednařík

2
@ChristianHackl、std::equal_toおよびstd::lessコンパレーターがパラメーターとして使用されるデフォルトのテンプレートパラメーターとして使用されます。operator==そしてoperator<、型が同等の同等および厳密な弱い順序をそれぞれ満たす必要がある場合に直接使用されます(例:イテレーターおよびランダムアクセスイテレーター)。
Jan Hudec、2015

28

これは、C ++標準ライブラリのコードを削除したコードです。

違う。これ C ++標準ライブラリremoveコードではありません。これは、C ++標準ライブラリの1つの可能な内部実装ですremove関数。C ++標準は実際のコードを規定していません。関数のプロトタイプと必要な動作を規定します。

言い換えると、厳密な言語の観点から、あなたが見ているコードは は存在しません。コンパイラの標準ライブラリの実装に付属しているヘッダーファイルからのものである可能性があります。C ++標準では、これらのヘッダーファイルが存在する必要さえありません。ファイルは、コンパイラーのインプリメンターが#include <algorithm>(たとえば、std::removeおよびその他の関数を使用可能にする)のような行の要件を満たすための便利な方法です。

if (!(*first == val))ではなく、なぜ不等式がテストされるのif (*first != val)ですか?

operator==関数でのみ必要なため。

カスタム型の演算子のオーバーロードに関しては、この言語を使用すると、あらゆる種類の奇妙なことを実行できます。オーバーロードされたクラスを作成することもできますoperator==いないoperator!=。またはさらに悪いことに、あなたは過負荷になる可能性operator!=がありますが、それは完全に無関係なことをするでしょう。

この例を考えてみましょう:

#include <algorithm>
#include <vector>

struct Example
{
    int i;

    Example() : i(0) {}

    bool operator==(Example const& other) const
    {
        return i == other.i;
    }

    bool operator!=(Example const& other) const
    {
        return i == 5; // weird, but nothing stops you
                       // from doing so
    }

};

int main()
{
  std::vector<Example> v(10);
  // ...
  auto it = std::remove(v.begin(), v.end(), Example());
  // ...
}

std::remove使用した場合operator!=、結果はかなり異なります。


1
考慮すべきもう一つは、それは両方のために可能であることであるa==ba!=b偽を返すように。そのような状況が「等しい」と「等しくない」のどちらとしてより意味があると見なされるかは必ずしも明確ではないかもしれませんが、「==」演算子のみで等価を定義する関数は、それらを「等しくない」と見なす必要があります"、どの振る舞いがより意味があるかに関係なく(もし私が他のドラスターを持っているなら、すべてのタイプはブール値を生成する" == "と"!= "演算子が一貫して動作することが期待されますが、IEEE-754が同等性の破れを義務付けているという事実オペレーターはそのような期待を困難にするでしょう。
スーパーキャット2015

18
「厳密な言語の観点から見ると、見ているコードは存在しません。」-視点が何かが存在しないと言っているが、実際にそれを見ている場合、視点は間違っています。実際、標準はコードが存在しないとは言わず、単にコードが存在するとは言わないだけです:-)
Steve Jessop

@SteveJessop:「厳密な言語の観点から」という表現を「厳密に、言語レベルで」のようなものに置き換えることができます。ポイントは、OPによってポストされたコードは言語標準の問題ではないということです。
クリスチャンハックル2015

2
@supercat:IEEE-754は一貫して作成==および!=動作しますが、6つすべての関係はfalse少なくとも1つのオペランドがであるときに評価されると常に思っていましたNaN
Ben Voigt 2015

@BenVoigt:ああ、そうです。これは、2つの演算子が同じように動作するようにして、互いに矛盾しないようにしますが、同等性に関連する他のすべての通常の公理に違反します(たとえば、a == aも、操作の実行も保証しません)等しい値では等しい結果が得られます)。
スーパーキャット2015

15

ここでいくつかの良い答え。ちょっとメモを追加したかっただけです。

すべての優れたライブラリと同様に、標準ライブラリは(少なくとも)2つの非常に重要な原則を念頭に置いて設計されています。

  1. あなたが逃げることができるあなたのライブラリーのユーザーに最小限の責任を負わせてください。これの一部は、インターフェースを使用するときに最小限の作業を提供することに関係しています。(あなたが逃げることができる限り少ない数の演算子を定義するように)。それの他の部分は、それらを驚くことではなく、またはエラーコードをチェックするように要求することと関係があります(そのため、インターフェイスの一貫性を保ち、問題が発生した<stdexcept>ときに例外をスローします)。

  2. すべての論理的な冗長性を排除します。すべての比較は、operator<ので、なぜユーザーに他のものを定義するよう要求するのでしょうか。例えば:

    (a> b)は(b <a)と同等

    (a> = b)は!(a <b)と同等

    (a == b)は!((a <b)||(b <a))と同等です。

    等々。

    もちろん、このノートでは、なぜ(少なくともデフォルトでは)ではなくがunordered_map必要なのかを質問するかもしれません。答えは、ハッシュテーブルで必要な比較は、等しいかどうかの比較だけです。したがって、等価演算子を定義するように要求する方が論理的に整合性があります(つまり、ライブラリユーザーにとってより理にかなっています)。を必要とするのは、なぜそれが必要なのかがすぐにはわからないため、混乱を招くでしょう。operator==operator<operator<


10
2番目の点について:論理的な順序が存在しない場合でも、等しいかどうかを論理的に比較できるタイプや、順序を確立することが非常に人為的なタイプがあります。たとえば、赤は赤であり、赤は緑ではありませんが、赤は本質的に緑よりも少ないですか?
クリスチャンハックル2015

1
完全に同意します。論理的な順序がないため、これらのアイテムを順序付けされたコンテナに格納しません。それらはoperator==(およびhash)を必要とする順序付けされていないコンテナーに格納する方が適切です。
Richard Hodges

3.過負荷少なくともできるだけ標準の演算子。これは、彼らが実装したもう1つの理由です!(a==b)。演算子の未反映のオーバーロードにより、C ++プログラムが完全にめちゃくちゃになる可能性があるためです(さらに、特定のバグの原因を見つけることがオデッセイに似ているため、コードのデバッグが不可能になる可能性があるため、プログラマーを夢中にさせることができます)。
syntaxerror

!((a < b) || (b < a))ブール演算子が1つ少ないため、おそらく高速になります
Filip Haglund、2015

1
実際にはそうではありません。アセンブリ言語では、すべての比較は減算として実装され、その後にフラグレジスタのキャリーとゼロビットがテストされます。他のすべては単なる構文上の砂糖です。
Richard Hodges

8

EqualityComparableコンセプトは唯一のことが必要operator==定義します。

その結果、満足のいくタイプを処理EqualityComparable することを宣言する関数operator!=は、そのタイプのオブジェクトの存在に依存できません。(の存在を暗示する追加の要件がない限りoperator!=)。


1

最も有望なアプローチは、operator ==が特定の型に対して呼び出されるかどうかを決定し、それが利用可能な場合にのみそれをサポートする方法を見つけることです。他の状況では、例外がスローされます。ただし、これまでのところ、任意の演算子式f == gが適切に定義されているかどうかを検出する既知の方法はありません。既知の最良のソリューションには、以下の望ましくない性質があります。

  • operator ==にアクセスできないオブジェクト(たとえば、プライベートであるため)のコンパイル時に失敗します。
  • operator ==の呼び出しがあいまいな場合、コンパイル時に失敗します。
  • operator ==がコンパイルされない場合でも、operator ==宣言が正しい場合は正しいように見えます。

Boost FAQから:ソース

==実装を要求することは負担であることがわかっているので、要求することで追加の負荷をかけたくない!=

私個人的には、SOLID(オブジェクト指向設計)L部分-リスコフ置換の原則についてです:「プログラム内のオブジェクトは、そのプログラムの正確さを変更することなく、サブタイプのインスタンスで置き換え可能でなければなりません。」この場合、ブール論理で==およびブール逆に置き換えることができるのは演算子!=です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.