C ++で構造体を比較しているときに==演算子が見つかりません


96

次の構造体の2つのインスタンスを比較すると、エラーが発生します。

struct MyStruct1 {
    MyStruct1(const MyStruct2 &_my_struct_2, const int _an_int = -1) :
        my_struct_2(_my_struct_2),
        an_int(_an_int)
    {}

    std::string toString() const;

    MyStruct2 my_struct_2;
    int an_int;
};

エラーは:

エラーC2678:バイナリ '==':タイプ 'myproj :: MyStruct1'の左側のオペランドをとる演算子が見つかりません(または許容できる変換がありません)

どうして?

回答:


126

C ++では、structsにはデフォルトで生成される比較演算子はありません。あなた自身で書く必要があります:

bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return /* your comparison code goes here */
}

21
@ジョナサン:なぜC ++はstructs を比較して等しいかどうかを知るのですか?簡単な方法が必要な場合はmemcmp、構造体にポインタが含まれていない場合が常にあります。
Xeo 2011

12
@Xeo:memcmp非PODメンバー(などstd::string)と埋め込み構造で失敗します。
fredoverflow

16
@ジョナサン私が知っている「現代の」言語は==演算子を提供します---ほとんど決して望まれることではないセマンティクスを備えています。(そして、それらはそれをオーバーライドする手段を提供しないので、メンバー関数を使用する必要があります)。私が知っている「現代の」言語も値のセマンティクスを提供しないため、適切でない場合でもポインターを使用せざるを得ません。
James Kanze、2011

4
@Jonathan Casesは、特定のプログラム内でも確かに異なります。エンティティオブジェクトの場合、Javaが提供するソリューションは非常にうまく機能します(もちろん、C ++でもまったく同じことができます---エンティティオブジェクトの慣用的なC ++でもかまいません)。問題は、値オブジェクトについて何をするかです。C ++は、operator=Cの互換性の理由から、デフォルトで(それが間違っていることが多い場合でも)提供しています。operator==ただし、Cの互換性にはは必要ありません。世界的には、Javaの動作よりもC ++の動作を好みます。(C#はわかりません。多分それが良いでしょう。)
James Kanze

9
少なくともそれ可能であるべき= defaultです!
user362515

93

C ++ 20は、デフォルトの比較、別名「宇宙船」をoperator<=>導入しました。これにより、コンパイラが生成した</ <=/ ==/ !=/ >=/および/または>演算子を明白な/ naive(?)実装で要求できます...

auto operator<=>(const MyClass&) const = default;

...しかし、より複雑な状況に合わせてカスタマイズできます(以下で説明します)。正当化と議論を含む言語提案については、こちらをご覧ください。この回答は、C ++ 17以前、およびoperator<=>... の実装をカスタマイズする必要がある場合の洞察に関連しています。

C ++がこれを以前に標準化していないと少し役に立たないように見えるかもしれませんが、多くの場合、構造体/クラスには比較から除外するデータメンバーがあります(たとえば、カウンター、キャッシュされた結果、コンテナー容量、最後の操作の成功/エラーコード、カーソル)。次のものを含むがこれらに限定されない無数の事柄について行う決定と同様に:

  • 最初に比較するフィールド。たとえば、特定のintメンバーを比較すると、map<string,string>一致しないオブジェクトの99%がすぐに削除される可能性があります。一方、メンバーは同じエントリを持ち、比較するのに比較的コストがかかる場合があります。値が実行時に読み込まれると、プログラマーは、コンパイラはおそらくできません
  • 文字列の比較:大文字と小文字の区別、空白とセパレータの同等性、エスケープ規則...
  • float / doubleを比較するときの精度
  • NaN浮動小数点値を等しいと見なすべきかどうか
  • ポインターまたはポイントされたデータの比較(後者の場合、ポインターが配列に対するものかどうか、および比較が必要なオブジェクト/バイトの数をどのようにして知るか)
  • 順序が重要かどうかソートされていないコンテナを比較する場合(例えばvectorlist)、そしてそれは、ソート一時に比較が行われるたびに余分なメモリを使用して対比較する前にその場でそれらをソートする大丈夫ですかもし
  • 比較する必要のある有効な値を現在保持している配列要素の数(どこかにサイズがあるのか​​、センチネルがあるのか​​)
  • union比較するaのメンバー
  • 正規化:たとえば、日付タイプは範囲外の日または月を許可する場合や、有理/小数オブジェクトは6/8を持ち、別のオブジェクトは3/4を持っている場合があります。独立した正規化ステップを遅延して; 比較の前に正規化をトリガーするかどうかを決定する必要がある場合があります
  • 弱いポインタが有効でない場合の対処法
  • operator==自身を実装しないメンバーとベースの処理方法(またはゲッターがあるcompare()operator<str()または持っている可能性があります...)
  • 他のスレッドが更新する可能性のあるデータの読み取り/比較中に取得する必要があるロック

したがって、特定の構造に対して比較が何を意味するかを明示的に考えて、コンパイル時に実行時に意味のある結果が得られないよりも、エラーが発生するのはいいことです

C ++は、あなたが言わせた場合、それはいいだろう、と述べているすべてのbool operator==() const = default;あなたが「ナイーブ」メンバーごとにメンバーを決めたのだとき==テストはした OK。も同じです!=。与えられた複数のメンバーは、/塩基は、「デフォルト」<<=>、および>=実装は絶望的ものの思える-宣言の可能性はなく、望んでいたことで、グループ化、塩基はメンバーの前に必ずいる(メンバーの順序のための要請を矛盾与えられているものであることが非常に低いのオーダーに基づいてカスケード依存性使用前のアクセシビリティ、構築/破壊)。より広く役立つためには、C ++は選択をガイドする新しいデータメンバー/ベースアノテーションシステムを必要とするでしょう。それ'

等値演算子の一般的な実装

もっともらしい実装

それはだ可能性が合理的かつ効率的な実装が可能であろう。

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.my_struct2 == rhs.my_struct2 &&
           lhs.an_int     == rhs.an_int;
}

これにはoperator==for MyStruct2も必要であることに注意してください。

この実装の影響、および代替案については、以下の「MyStruct1の詳細の説明」という見出しの下で説明します。

==、<、> <=などへの一貫したアプローチ

std::tupleの比較演算子を利用して独自のクラスインスタンスを比較するのは簡単std::tieです。目的の比較順序でフィールドへの参照のタプルを作成するのに使用するだけです。ここから私の例を一般化:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) ==
           std::tie(rhs.my_struct2, rhs.an_int);
}

inline bool operator<(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) <
           std::tie(rhs.my_struct2, rhs.an_int);
}

// ...etc...

比較したいクラス、特にC ++ 14がreturnステートメントから関数の戻り値の型を推測できるように準備している(つまり、企業およびサードパーティのライブラリの要素を編集できる)場合は、多くの場合、 "メンバー関数を、比較したいクラスに結び付けます。

auto tie() const { return std::tie(my_struct1, an_int); }

次に、上記の比較は次のように単純化されます。

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.tie() == rhs.tie();
}

比較演算子の完全なセットが必要な場合は、ブースト演算子(を検索less_than_comparable)をお勧めします。なんらかの理由で不適切な場合は、サポートマクロ(オンライン)のアイデアが気に入る場合と気に入らない場合があります。

#define TIED_OP(STRUCT, OP, GET_FIELDS) \
    inline bool operator OP(const STRUCT& lhs, const STRUCT& rhs) \
    { \
        return std::tie(GET_FIELDS(lhs)) OP std::tie(GET_FIELDS(rhs)); \
    }

#define TIED_COMPARISONS(STRUCT, GET_FIELDS) \
    TIED_OP(STRUCT, ==, GET_FIELDS) \
    TIED_OP(STRUCT, !=, GET_FIELDS) \
    TIED_OP(STRUCT, <, GET_FIELDS) \
    TIED_OP(STRUCT, <=, GET_FIELDS) \
    TIED_OP(STRUCT, >=, GET_FIELDS) \
    TIED_OP(STRUCT, >, GET_FIELDS)

...それはアラに使用できます...

#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)

(C ++ 14メンバータイバージョンはこちら

MyStruct1の詳細についての議論

自立型とメンバー型のどちらを選択するかには影響がありoperator==()ます...

独立した実装

あなたには興味深い決断があります。クラスはから暗黙的に構築できるためMyStruct2、独立型/非メンバーbool operator==(const MyStruct2& lhs, const MyStruct2& rhs)関数がサポートします...

my_MyStruct2 == my_MyStruct1

...最初にMyStruct1から一時ファイルを作成しmy_myStruct2、次に比較を行います。これは間違いなくMyStruct1::an_int、コンストラクタのデフォルトパラメータ値に設定したままにします-1。あなたが含まれているか否かに応じて、an_intあなたの実装で比較operator==MyStruct1かもしれないのかは等しいとしない場合がありますMyStruct2自体がと等しいことMyStruct1my_struct_2メンバーを!さらに、MyStruct1既存のmy_struct2メンバーを一時ファイルにコピーし、比較後に破棄するだけなので、一時ファイルの作成は非常に非効率的な操作になる可能性があります。(もちろん、コンストラクターをMyStruct1作成するexplicitか、のデフォルト値を削除することで、この暗黙的なsの構築を回避して比較できますan_int。)

メンバーの実装

MyStruct1からのaの暗黙的な構築を回避する場合はMyStruct2、比較演算子をメンバー関数にします。

struct MyStruct1
{
    ...
    bool operator==(const MyStruct1& rhs) const
    {
        return tie() == rhs.tie(); // or another approach as above
    }
};

constキーワード(メンバーの実装にのみ必要)に注意してください。オブジェクトを比較してもオブジェクトは変更されないため、constオブジェクトで許可できることをコンパイラーに通知します。

可視表現の比較

必要な種類の比較を取得する最も簡単な方法は、

    return lhs.to_string() == rhs.to_string();

...多くの場合、これも非常に高価です。これらはstring、捨てられるだけのために痛々しく作成されています。浮動小数点値を持つ型の場合、可視表現を比較することは、表示される桁数が許容差を決定することを意味し、その範囲内でほぼ等しい値が比較中に等しいものとして扱われます。


まあ、実際には比較演算子<、>、<=、> =の場合、<を実装することだけが必要です。残りは以下のとおりです。それらを実装する意味のある方法はなく、自動的に生成できる実装とは異なるものを意味します。すべてを自分で実装しなければならないのは奇妙です。
アンドレ

アンドレ@:より頻繁に手動で書かれたint cmp(x, y)またはcompare負の値を返す関数x < y、0平等と正の値のためのx > yための基礎として使用され<><=>===、および!=、CRTPを使用して、これらのすべての演算子をクラスに挿入するのは非常に簡単です。私は古い答えで実装を投稿したと確信していますが、すぐにそれを見つけることができませんでした。
Tony Delroy、2015年

@TonyD確かにあなたはそれを行うことができますが、それだけで実装しやすいようである><=>=の観点から<。あなたは可能性も実装==および!=その方法、それは通常、私は推測する非常に効率的な実装ではありません。これらすべてにCRTPやその他のトリックが必要ない場合は便利ですが、ユーザーが明示的に定義して定義していない場合は、標準でこれらの演算子の自動生成が義務付けられます<
アンドレ・

アンドレ・@:それはので、だ==!=効率的に使用して表現されない可能性があります<すべてのための比較使用することが一般的であること。 「CRTPまたは他のトリック何が必要とされない可能ならばそれはいいだろう」 -おそらく、しかし、その後CRTPは簡単に例えばビット単位(他の事業者の多くを生成するために使用することができ|&^から|=&=そして^=+ - * / %彼らの割り当てフォームから、バイナリ-単項否定からと+)-このテーマの非常に多くの潜在的に有用なバリエーションであり、その非常に恣意的なスライスの1つに言語機能を提供するだけでは特にエレガントではありません。
Tony Delroy、2015年

複数のメンバーの比較を行うために使用するバージョンを、もっともらしい実装に追加していただけませんstd::tieか?
NathanOliver

17

あなたは明示的に定義する必要がありますoperator ==ためMyStruct1

struct MyStruct1 {
  bool operator == (const MyStruct1 &rhs) const
  { /* your logic for comparision between "*this" and "rhs" */ }
};

==の比較は、このような2つのオブジェクトに対して有効です。


11

C ++ 20以降では、次のようにデフォルトの3者間比較演算子(「スペースシップ」演算子)を宣言することにより==、デフォルトの比較演算子(<=、など)の完全なセットをクラスに追加できるはずです。

struct Point {
    int x;
    int y;
    auto operator<=>(const Point&) const = default;
};

準拠するC ++ 20コンパイラでは、MyStruct1とMyStruct2にその行を追加するだけで、MyStruct2の定義に互換性があると仮定して、等価比較が可能になる場合があります。



2

デフォルトでは、構造体には==演算子がありません。独自の実装を作成する必要があります。

bool MyStruct1::operator==(const MyStruct1 &other) const {
    ...  // Compare the values, and return a bool result.
  }

0

デフォルトでは、==演算子はプリミティブに対してのみ機能します。コードを機能させるには、構造体の==演算子をオーバーロードする必要があります。


0

構造体の比較演算子を作成しなかったためです。コンパイラーはそれを生成しないので、比較が必要な場合は、自分で作成する必要があります。

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