==と!=は相互に依存していますか?


292

私はC ++で演算子オーバーロードについて学んだ、と私はそれを見る==!=、単純にユーザー定義型用にカスタマイズすることができますいくつかの特別な機能です。しかし、私の懸念は、なぜ2つの別個の定義が必要なのかということです。私があればと思ったa == b事実である場合、a != b定義によって、あるため、自動的に偽の、およびその逆で、かつ、他の可能性がないa != bです!(a == b)。そして、これが真実ではない状況を想像することはできませんでした。しかし、おそらく私の想像力は限られていますか、それとも私は何かについて無知ですか?

もう1つの観点から1つを定義できることは知っていますが、これは私が求めていることではありません。また、値による比較や同一性によるオブジェクトの比較の違いについても質問していません。または、2つのオブジェクトが同時に等しいか等しくないかどうか(これは間違いなくオプションではありません!これらは相互に排他的です)。私が尋ねているのはこれです:

2つのオブジェクトが等しいことについて質問しても意味がないが、等しくないことについて質問しても意味がない状況はありますか?(ユーザーの観点から、または実装者の観点から)

このような可能性がない場合、なぜC ++ではこれら2つの演算子が2つの異なる関数として定義されているのでしょうか。


13
2つのポインタは両方ともnullである可能性がありますが、必ずしも等しいとは限りません。
Ali Caglayan 2016年

2
ここで意味があるかどうかはわかりませんが、これを読んで「短絡」の問題を思いました。たとえば、'undefined' != expression式を評価できるかどうかに関係なく、常に真(または偽、または未定義)であると定義できます。この場合a!=b、定義に従って正しい結果が返されますが、評価できない!(a==b)場合は失敗しbます。(または、評価bに費用がかかる場合は時間がかかります)。
Dennis Jaheruddin 2016年

2
null!= nullおよびnull == nullはどうですか?それは両方になる可能性があります...したがって、a == bであるとは限らないa!= bの場合
zozo

4
JavaScriptの例(NaN != NaN) == true
chiliNUT '19年

回答:


272

あなたは考えていない言語が自動的に書き換えたいa != b!(a == b)する場合a == b以外のリターンは何かbool。そして、あなたがそれをそうするかもしれないいくつかの理由があります。

式ビルダーオブジェクトを使用している場合a == bがあります。比較を実行することを意図しておらず、意図したものではありませんが、を表す式ノードを構築するだけa == bです。

遅延a == b比較がある場合があります。比較を直接実行することは意図しておらず、意図されていませんが、代わりに、後で実際に比較を実行するためlazy<bool>bool暗黙的または明示的に変換できる種類の結果を返します。おそらく式ビルダーオブジェクトと組み合わせて、評価前に完全な式の最適化を可能にします。

optional<T>オプションの変数tとを指定してu、許可したいt == uが、それを返すようにするカスタムテンプレートクラスがある場合がありますoptional<bool>

おそらく私が思いもしなかったことがもっとあるでしょう。また、これらの例では、操作a == ba != b実行の両方に意味がありますa != bが、それでもと同じではない!(a == b)ため、個別の定義が必要です。


72
式の作成は、これが必要な場合の素晴らしい実用的な例です。これは、人為的なシナリオに依存しません。
Oliver Charlesworth 2016年

6
別の良い例は、ベクトル論理演算です。あなたはかなりのデータを一回通過では、コンピューティングと思います!=代わりに、コンピューティング二つのパスで==、その後!。特に、ループを融合するためにコンパイラーに頼ることができなかった時代に戻ります。あるいは、今日でも、コンパイラーを納得させることができない場合でも、ベクトルは重複しません。

41
「あなたは式ビルダーオブジェクトを持っているかもしれません」-それからオペレーター!はいくつかの式ノードを構築することもできますが、それでも限りではで置き換えa != bても問題ありません!(a == b)。同じことはlazy<bool>::operator!、それは戻ることができlazy<bool>ます。optional<bool>たとえばの論理的真実性はboost::optional、値自体ではなく、値が存在するかどうかに依存するため、より説得力があります。
Steve Jessop 2016年

42
すべて、そしてNans-sを覚えておいてください NaN
jsbueno 2016年

9
@jsbueno:NaNはこの点で特別ではないことが、さらに下で指摘されています。
Oliver Charlesworth 2016年

110

このような可能性がない場合、なぜC ++ではこれら2つの演算子が2つの異なる関数として定義されているのでしょうか。

あなたはそれらをオーバーロードすることができ、それらをオーバーロードすることにより、それらに元のものとは全く異なる意味を与えることができます。

たとえば、<<元はビット単位の左シフト演算子であるoperatorを考えますが、現在では、のように挿入演算子としてオーバーロードされていstd::cout << somethingます。オリジナルとは意味が全然違う。

したがって、演算子をオーバーロードしたときに演算子の意味が変わることを受け入れる場合、混乱を招く可能性がありますが、ユーザーがoperator ==否定ではない意味を演算子に与えないようにする理由はありません!=


18
これが実用的な意味を持つ唯一の答えです。
Sonic Atom

2
私には、あなたには原因と結果が逆にあるようです。別個の演算子として存在するため==、それらを個別にオーバーロードでき!=ます。一方、それらは個別にオーバーロードできるため、別個の演算子としては存在しない可能性がありますが、レガシーおよび利便性(コードの簡潔さ)の理由によります。
nitro2k01

60

しかし、私の懸念は、なぜ2つの異なる定義が必要なのかということです。

両方を定義する必要はありません。
それらが相互に排他的である場合でも、std :: rel_opsを定義し==<それと一緒に定義するだけで、簡潔にすることができます。

FOM cppreference:

#include <iostream>
#include <utility>

struct Foo {
    int n;
};

bool operator==(const Foo& lhs, const Foo& rhs)
{
    return lhs.n == rhs.n;
}

bool operator<(const Foo& lhs, const Foo& rhs)
{
    return lhs.n < rhs.n;
}

int main()
{
    Foo f1 = {1};
    Foo f2 = {2};
    using namespace std::rel_ops;

    //all work as you would expect
    std::cout << "not equal:     : " << (f1 != f2) << '\n';
    std::cout << "greater:       : " << (f1 > f2) << '\n';
    std::cout << "less equal:    : " << (f1 <= f2) << '\n';
    std::cout << "greater equal: : " << (f1 >= f2) << '\n';
}

2つのオブジェクトが等しいことについて質問しても意味がないが、等しくないことについて質問しても意味がない状況はありますか?

これらの演算子を平等に関連付けることがよくあります。
これが基本型での動作ですが、これがカスタムデータ型での動作である必要はありません。したくない場合は、ブール値を返す必要はありません。

私は人々が奇妙な方法でオペレーターに過負荷をかけているのを見てきましたが、ドメイン固有のアプリケーションにとってそれが理にかなっていることを発見しただけです。インターフェースが相互に排他的であることを示すように見えても、作成者は特定の内部ロジックを追加したい場合があります。

(ユーザーの視点から、または実装者の視点から)

特定の例が必要であることはわかっているので、実用的だと思っ
Catchテストフレームワークの例を以下に示します。

template<typename RhsT>
ResultBuilder& operator == ( RhsT const& rhs ) {
    return captureExpression<Internal::IsEqualTo>( rhs );
}

template<typename RhsT>
ResultBuilder& operator != ( RhsT const& rhs ) {
    return captureExpression<Internal::IsNotEqualTo>( rhs );
}

これらの演算子はさまざまなことを行っており、一方のメソッドを他方の!(not)として定義しても意味がありません。これが行われる理由は、フレームワークが行われた比較を出力できるようにするためです。これを行うには、どのオーバーロードされた演算子が使用されたかのコンテキストをキャプチャする必要があります。


14
ああ、どうして私はどうして知りませんstd::rel_ops?指摘いただきありがとうございます。
Daniel Jour 2013年

5
cppreference(または他の場所)からの逐語的コピーは明確にマークされ、適切に属性付けされている必要があります。rel_opsとにかくひどいです。
TC

@TC同意、私はそれがOPが取ることができる方法を言っているだけです。示されている例よりも単純なrel_opsの説明方法がわかりません。私はそれがどこにあるかにリンクしましたが、リファレンスページは常に変更される可能性があるため、コードを投稿しました。
Trevor Hickey

4
それでも、コード例が独自のものではなく、cppreferenceから99%であることを明確にする必要があります。
TC

2
Std :: relopsは支持されなくなったようです。もっとターゲットを絞ったものについては、ブーストオプスをチェックしてください。
JDługosz

43

そこにいくつかの非常によく確立された慣習がある(a == b)(a != b)されている両方の偽は必ずしも反対ではありません。特にSQLでは、NULLと比較すると、trueまたはfalseではなく、NULLが生成されます。

直感的ではないため、この例を可能な限り新しく作成することはおそらくお勧めできませんが、既存の規則をモデル化しようとしている場合は、そのために演算子を「正しく」動作させるオプションがあると便利です。環境。


4
SQLのようなnull動作をC ++に実装しますか?うわぁ しかし、私はそれが言語で禁止されるべきであると私が思うものではないと思いますが、それは不愉快かもしれません。

1
@ dan1111さらに重要なことに、SQLのいくつかのフレーバーはc ++でコード化される可能性があるため、言語は構文をサポートする必要がありますか?
Joe

1
私が間違っている場合は修正してください。ここではウィキペディアから抜け出しますが、SQLのNULL値と比較すると、FalseではなくUnknownが返されませんか?そして、不明の否定はまだ不明ではないのですか?したがって、SQLロジックがC ++でコーディングされているNULL == something場合は、Unknownを返したくありません。またNULL != something、Unknownを返したいので、!Unknownを返しUnknownます。そしてその場合operator!=の否定として実装することoperator==はまだ正しいです。
ベンジャミンリンドリー2016年

1
@Barmar:わかりましたが、それによって「SQL NULLはこのように機能する」というステートメントがどのように正しくなりますか?比較演算子の実装をブール値を返すように制限している場合、それはこれらの演算子を使用したSQLロジックの実装が不可能であることを意味するだけではありませんか?
ベンジャミンリンドリー2016年

2
@Barmar:いいえ、それは重要ではありません。OPは既にその事実を知っているか、この質問は存在しません。重要なのは、1)operator==またはのどちらか一方を実装するoperator!=が、他方は実装operator!=しない、または2)の否定以外の方法で実装することが理にかなっている例を示すことでしたoperator==。また、NULL値のSQLロジックを実装することは、そうではありません。
ベンジャミンリンドリー2016年

23

私はあなたの質問の2番目の部分、つまり次の部分のみに回答します。

このような可能性がない場合、なぜC ++ではこれら2つの演算子が2つの異なる関数として定義されているのでしょうか。

開発者が両方をオーバーロードできるようにすることが理にかなっている1つの理由は、パフォーマンスです。==との両方を実装することで、最適化を許可でき!=ます。そうなるとx != y、安くなるかもしれません!(x == y)。一部のコンパイラーは最適化できる場合がありますが、特に、多くの分岐が関係する複雑なオブジェクトがある場合は最適化できません。

開発者が法則と数学的概念を非常に真剣に考えているHaskellでさえ、こちら(http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude)を見るとわかるように、==との両方をオーバーロードすることが依然として許可さ/=れています。.html#v:-61--61-):

$ ghci
GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
λ> :i Eq
class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool
        -- Defined in `GHC.Classes'

これはおそらくマイクロ最適化と見なされますが、場合によっては正当化されることがあります。


3
SSE(x86 SIMD)ラッパークラスは、この良い例です。pcmpeqb命令はありますが、!=マスクを生成するパック比較命令はありません。したがって、結果を使用するもののロジックを単に逆にすることができない場合は、別の命令を使用してそれを逆にする必要があります。(楽しい事実:AMDのXOP命令セットにはのパックされた比較がありneqます。IntelがXOPを採用/拡張しなかったのは残念です。ISAの拡張機能が間もなくなくなるISA拡張にいくつかの有用な命令があります。)
Peter Cordes

1
そもそもSIMDの要点はパフォーマンスであり、通常、全体的なパフォーマンスにとって重要なループで手動で使用するだけです。PXORタイトなループで単一の命令(比較マスクの結果を反転するすべて1の命令)を保存することが重要な場合があります。
Peter Cordes

オーバーヘッドが1つの論理的な否定である場合、理由としてのパフォーマンスは信頼できません。
乾杯とhth。-Alf

コンピューティングのx == yコストがを大幅に上回る場合、それは複数の論理否定である可能性がありますx != y。後者の計算は、分岐予測などにより大幅に安くなる可能性があります
Centril

16

2つのオブジェクトが等しいことについて質問しても意味がないが、等しくないことについて質問しても意味がない状況はありますか?(ユーザーの視点から、または実装者の視点から)

それは意見です。多分それはしません。しかし、言語デザイナーは、全知ではなく、それが理にかなっているかもしれない状況を思い付くかもしれない人々を制限しないことに決めました(少なくとも彼らにとって)。


13

編集に応じて;

それはオペレータ持っているいくつかのタイプのために可能であればそれは、ある==ではなく!=、またはその逆、とするとき、それはそうしても意味がないし。

では、一般的な、いや、それは意味がありません。等値演算子と関係演算子は通常、セットで提供されます。平等がある場合は、不平等も同様です。より小さい、次により大きい<=などなど。算術演算子にも同様のアプローチが適用されますが、それらも一般に自然な論理セットになります。

これはstd::rel_ops名前空間で証明されています。等しい演算子とより小さい演算子を実装する場合、その名前空間を使用すると、元の実装された演算子に関して実装された他の名前空間が得られます。

はいえ、一方が他方をすぐに意味しない、または他の観点から実装できない状況または状況はありますか?はい、間違いなく少数ですが、彼らはそこにいます。再び、rel_opsそれ自体が独自の名前空間であることからも明らかです。そのため、それらを個別に実装できるようにすると、言語を活用して、コードのユーザーまたはクライアントにとって自然で直感的な方法で、必要または必要なセマンティクスを取得できます。

すでに述べた遅延評価は、この優れた例です。別の良い例は、平等または不平等をまったく意味しない意味論を彼らに与えることです。これと同様の例はビットシフト演算子で<<あり>>、ストリームの挿入と抽出に使用されます。それは一般的なサークルでは眉をひそめられるかもしれませんが、一部のドメイン固有の領域では、それは理にかなっているかもしれません。


12

==and !=演算子が実際に等しいことを意味しない場合は、and 演算子<<>>stream演算子がビットシフトを意味しないのと同じように。シンボルを他の概念を意味するものとして扱う場合、それらは相互に排他的である必要はありません。

同等性の観点から、ユースケースでオブジェクトを比較不可能なものとして扱う必要がある場合、すべての比較でfalse(または演算子が非ブール値を返す場合は比較不可能な結果タイプ)を返す必要がある場合に意味があります。これが正当化される特定の状況を考えることはできませんが、十分妥当であることがわかりました。


7

優れた力には責任を持って、または少なくとも本当に優れたスタイルガイドが備わっています。

==そして!=、あなたが好きな一体を行うためにオーバーロードすることができます。それは祝福と呪いの両方です。という!=意味の保証はありません!(a==b)


6
enum BoolPlus {
    kFalse = 0,
    kTrue = 1,
    kFileNotFound = -1
}

BoolPlus operator==(File& other);
BoolPlus operator!=(File& other);

この演算子のオーバーロードを正当化することはできませんが、上の例ではoperator!=の「反対」として定義することは不可能ですoperator==



1
@Snowman:Dafangは、その優れた列挙型(そのような列挙型を定義するのも良い考え)を言っていません。これは、ポイントを説明するための単なる例です。この(おそらく悪い)演算子の定義で!=は、実際にはの逆は意味しません==
AlainD

1
@AlainD投稿したリンクをクリックしましたか。そのサイトの目的を知っていますか?これは「ユーモア」と呼ばれます。

1
@スノーマン:私は確かにそうします...すみません、それはリンクであり、皮肉として意図されていなかった!:o)
AlainD 2016年

待って、あなたは単項をオーバーロードしてい==ますか?
LF

5

最後に、これらの演算子で確認しているのは、式a == bまたはa != bブール値(trueまたはfalse)を返すことです。これらの式は、相互に排他的ではなく、比較後にブール値を返します。


4

[..]なぜ2つの異なる定義が必要なのですか?

考慮すべきことの1つは、これらの演算子の1つを、他の演算子の否定を使用するよりも効率的に実装できる可能性があることです。

(ここでの私の例はごみですが、要点はまだあります。たとえば、ブルームフィルターについて考えてみてください。これらは、セットに含まれていないものの高速テストを可能にしますが、入っているかどうかのテストにはさらに時間がかかる場合があります。)

[..]定義により、a != bです!(a == b)

そして、それを成立させるのはプログラマとしてのあなたの責任です。たぶんテストを書くのに良いことでしょう。


4
どの!((a == rhs.a) && (b == rhs.b))ように短絡を許可しないのですか?の場合!(a == rhs.a)(b == rhs.b)評価されません。
ベンジャミンリンドリー2016年

しかし、これは悪い例です。ここでの短絡は魔法の利点を追加しません。
Oliver Charlesworth 2016年

@Oliver Charlesworth Aloneはそうではありませんが、個別の演算子と結合すると、そうなります。の場合==、最初の対応する要素が等しくなくなるとすぐに比較が停止します。しかし、の場合!=、それがに関して実装された場合==、対応するすべての要素を最初に比較して(それらがすべて等しい場合)、それらが等しくないことを通知できるようにする必要があります。上記の例では、最初の等しくないペアが見つかるとすぐに比較が停止します。確かに素晴らしい例です。
BarbaraKwarc

@BenjaminLindley確かに、私の例は完全にナンセンスでした。残念ながら、私は別のatmを思い付くことはできません。ここでは手遅れです。
Daniel Jour 2013年

1
@BarbaraKwarc:!((a == b) && (c == d))(a != b) || (c != d)短絡効率の点で同等です。
Oliver Charlesworth 2016年

2

オペレーターの振る舞いをカスタマイズすることにより、オペレーターに必要なことを実行させることができます。

カスタマイズしたいかもしれません。たとえば、クラスをカスタマイズしたい場合があります。このクラスのオブジェクトは、特定のプロパティをチェックするだけで比較できます。これが事実であることを知って、オブジェクト全体のすべての単一のプロパティのすべてのビットをチェックする代わりに、最小限のものだけをチェックする特定のコードを書くことができます。

何かが同じであることがわかるよりも、速くないにしても同じくらい速く何かが異なることを理解できるケースを想像してみてください。確かに、何かが同じか異なるかがわかれば、ビットを反転するだけでその反対を知ることができます。ただし、そのビットを反転することは追加の操作です。場合によっては、コードが何度も再実行されるときに、1つの操作を保存すると(何倍にもなる)、全体的な速度が向上します。(たとえば、メガピクセル画面のピクセルごとに1つの操作を保存すると、100万の操作が保存されます。毎秒60画面を掛けると、さらに多くの操作を保存できます。)

hvdの答えはいくつかの追加の例を提供します。


2

はい。1つは「同等」を意味し、もう1つは「非同等」を意味し、この用語は相互に排他的です。この演算子の他の意味は混乱を招くため、絶対に避けてください。


それらはすべての場合に相互に排他的ではありません。たとえば、2つの無限大は互いに等しくなく、互いに等しくありません。
vladon

@vladonは一般的なケースで他の代わりに使用できますか?いいえ。これは、それらが等しくないことを意味します。残りはすべて、operator == /!=ではなく特別な関数に送られます
oliora

@vladonください。一般的なケースの代わりに、私の回答のすべてのケースを読んでください。
oliora 2016年

@vladonこれは数学ではそうですが、Cでこの理由によりa != bと等しくない例を挙げられます!(a == b)か?
nitro2k01

2

多分比類のないルールで、どこa != bで、ステートレスビットのようa == bでした。

if( !(a == b || a != b) ){
    // Stateless
}

論理記号を再配置する場合、!([[A] || [B])は論理的に([!A]&[!B])になります
Thijser

ノートの戻り値の型ものoperator==()operator!=()は必ずしもされていないbool、彼らはあなたが、まだ演算子はまだ定義されるかもしれないが、そうすることを望んでいた場合にはステートレスなどが列挙型であるかもしれない(a != b) == !(a==b)。..保持している
lorro
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.