web-dev-qa-db-ja.com

演算子<のみを実装している場合、演算子==を使用できますか?

特定のオブジェクトに_operator<_を実装しました。論理的に、!(a < b)および!(b < a)の場合、_a == b_を意味します。

これは自動的に推測されますか? _==_のみを実装する場合、_<_を使用できますか?

51
Eyzuky

C++は、いくつかの理由でこれを自動的に推測できません。

  1. すべての単一の型を_operator<_と比較することは意味がないため、その型は必ずしも_operator<_。を定義するとは限りません。
    • これは、_operator==_を_operator<_に関して自動的に定義できないことを意味します
  2. _operator<_は、引数を比較する必要はありません。プログラマーは、引数に対してほとんど何でもを行うタイプの演算子を定義できます。
    • これは、!(a < b) && !(b < a)が_a == b_と同等であるというステートメントは、それらの演算子が定義されていると仮定すると必ずしも真実ではない可能性があることを意味します。

タイプに_operator==_関数が必要な場合は、自分で定義するだけです。そんなに難しくない:)

_// For comparing, something like this is used
bool operator==(const MyType& lhs, const MyType& rhs)
{
    // compare (or do other things!) however you want
}

// ... though it's not the only thing you can do
//  - The return type can be customised
//  - ... as can both of the arguments
const MyType& operator==(int* lhs, const MyType* const rhs)
{
    return lhs;
}
_
69
user7881131

== のように、すべてのタイプが順序付けられているわけではないため、<からstd::complexを推測できません。 2 + 3i > 1 + 4iですか?

さらに、通常順序付けられている型であっても、>または<から等値を推測することはできません。たとえば、 IEEE-754 NaN

double n = std::numeric_limits<double>::quiet_NaN();

std::cout << "NaN == NaN: " << (n == n) << '\n';
std::cout << "NaN < NaN: " << (n < n) << '\n';
std::cout << "NaN > NaN: " << (n > n) << '\n';
std::cout << "NaN != NaN: " << (n != n) << '\n';

最後の例外を除き、すべてfalseを返します

48
phuclv

いいえ。このメソッドは、 完全に順序付けられた と呼ばれる数値のようなオブジェクトでうまく機能します。すべての種類のセット/クラスについて、誰もこの関係を保証できません。 operator <が何かを比較することを誰も保証できません。

したがって、====に他なりません。 ==<を実装できますが、これはすべての人に有効ではなく、C++標準はあなたのためにそれを行いません。

18
Shitao Zhou

C++はこれを自動的に推測しません。 _operator>_、_operator<=_、および_operator>=_には、 _std::rel_ops_ を使用できます。これには_operator<_のみが必要です。ただし、_operator==_に関して_operator<_は提供しません。次のように自分でこれを行うことができます。

_template <class T>
bool operator==(T const& lhs, T const& rhs)
{
    return !((lhs < rhs) or (rhs < lhs));
}
_

!((lhs < rhs) or (rhs < lhs))!(lhs < rhs) and !(rhs < lhs)は数学的に同等であることに注意してください。

12
Jonas

論理的に、!(a <b)および!(b <a)の場合、a == bを意味します。 C++はこれを自動的に推測しますか?実装しただけの場合==を使用できますか

他の人が数学的用語で述べていることを言えば:boolを返す_operator <_があり、strict weak orderを定義していると仮定し、実装します_operator ==_が!(a < b) && !(b < a)を返す場合、この演算子は、指定された厳密な弱順序と一致する等価関係を定義します。ただし、C++では、厳密な弱い順序を定義する_operator <_も、同値関係を定義する_operator ==_も必要ありません(ただし、sortなどの標準アルゴリズムの多くは、これらの演算子を暗黙的に使用し、厳密な弱次rsp。等価関係)。

_operator <_の厳密な弱い順序に基づいて一貫した他のすべての関係演算子を定義する場合、Boost.Operatorsを使用すると入力を節約できます。

標準アルゴリズムの要件を満たさない_operator <_を誤用するのはとても簡単だからです。誤って_std::sort_、_std::lower_bound_などで使用することにより、_operator <_を厳密な弱い順序として定義するか、まったく定義しないことをお勧めします。 CodesInChaosが示した例は部分的な順序であり、厳密な弱い順序の「比較不能の推移性」要件を満たしていません。したがって、このような関係を別の名前で呼び出すことをお勧めします。 bool setLess(const MySet &, const MySet &)

ソース:

12
Arne Vogel

多くの人が述べているように、あなたはできませんし、コンパイラもそうすべきではありません。

これは、<から==に至るまでeasyであってはならないという意味ではありません。

boost :: operators 簡単にしようとします。それを使用して完了しました。

あなたがそれを自分でやりたいなら、boostが提供するものを再実装するのにほんの少しのコードしか必要としません:

namespace utility {
  namespace details {
    template<class...>using void_t=void;
    template<template<class...>class Z, class, class...Ts>
    struct can_apply:std::false_type{};
    template<template<class...>class Z, class...Ts>
    struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};
  }
  template<template<class...>class Z, class...Ts>
  using can_apply = ::utility::details::can_apply<Z,void,Ts...>;
}

namespace auto_operators {
  template<class T, class U>
  using less_r = decltype( std::declval<T const&>() < std::declval<U const&>() );
  template<class T, class U>
  using can_less = ::utility::can_apply<less_r, T, U>;

  struct order_from_less {
    template<class T, class U>
    using enabled = std::enable_if_t<
      std::is_base_of<order_from_less, T>{}
      && std::is_base_of<order_from_less, U>{}
      && can_less<T, U>{},
      bool
    >;
    template<class T, class U>
    friend enabled<U,T>
    operator>(T const& lhs, U const& rhs) {
      return rhs < lhs;
    }
    template<class T, class U>
    friend enabled<U,T>
    operator<=(T const& lhs, U const& rhs) {
      return !(lhs > rhs);
    }
    template<class T, class U>
    friend enabled<T,U>
    operator>=(T const& lhs, U const& rhs) {
      return !(lhs < rhs);
    }
  };
  struct equal_from_less:order_from_less {
    template<class T, class U>
    using enabled = std::enable_if_t<
      std::is_base_of<order_from_less, T>{}
      && std::is_base_of<order_from_less, U>{}
      && can_less<T, U>{} && can_less<U,T>{},
      bool
    >;
    template<class T, class U>
    friend enabled<U,T>
    operator==(T const& lhs, U const& rhs) {
      return !(lhs < rhs) && !(rhs < lhs);
    }
    template<class T, class U>
    friend enabled<U,T>
    operator!=(T const& lhs, U const& rhs) {
      return !(lhs==rhs);
    }
  };
}

上記の記述は1回だけ、または#include boostから取得した同等のコードのみで十分です。

ブーストまたは上記を取得したら、次のように簡単です:

struct foo : auto_operators::equal_from_less {
  int x;
  foo( int in ):x(in) {}
  friend bool operator<( foo const& lhs, foo const& rhs ) {
    return lhs.x < rhs.x;
  }
};

fooには、すべての順序付け演算子と比較演算子が定義されています。

int main() {
  foo one{1}, two{2};
  std::cout << (one < two) << "\n";
  std::cout << (one > two) << "\n";
  std::cout << (one == two) << "\n";
  std::cout << (one != two) << "\n";
  std::cout << (one <= two) << "\n";
  std::cout << (one >= two) << "\n";
  std::cout << (one == one) << "\n";
  std::cout << (one != one) << "\n";
  std::cout << (one <= one) << "\n";
  std::cout << (one >= one) << "\n";
}

ライブの例

このすべてのポイントは、C++が言語として、<>>===がすべて意味をなすと想定していないことです。ただし、<が定義された型を取得できるライブラリを作成し、簡単な基​​本クラスを追加すると、これらの他のすべての操作がランタイムコストなしで突然定義されます。

コンパイラは、==から<を推測しません。

簡単な例で確認できます:

#include <iostream>

struct A {
    A(int r):i{r}{}
    int i;
};

bool operator<(A const & a1, A const& a2) {
    return a1.i < a2.i;
}

int main(int argc, char* argv[]) {
    A a1{2};
    A a2{3};
    if(a1 == a2) {
        std::cout << "equals\n";
    }
    return 0;
}

GCCは次のエラーを表示します。

main.cpp:20:11: error: no match for 'operator==' (operand types are 'A' and 'A')

     if(a1 == a2) {
6
nefas

std::rel_ops 欠落している演算子を自動定義している名前空間。

必要に応じて、less演算子に基づいて等価演算子を定義しません。

それでもこれは非常に便利です。 less演算子とequal演算子を定義すると、他の比較演算子は無料で使用できます。

6
Marek R

答えはノーです。簡単なテストが必要です。

struct MyType{
    int value;
};

bool operator < (MyType& a, MyType& b)
{
    return a.value < b.value;
}

int main(int argc, char* argv[])
{
    MyType a = {3};
    MyType b = {4};
    if (a == b)
        std::cout << "a==b" << std::endl;
    if (a < b)
        std::cout << "a < b" << std::endl;
}

g ++ 4.8.2の苦情:

main.cpp:関数「int main(int、char **)」:

main.cpp:16:11:エラー:「operator ==」に一致しません(オペランドタイプは「MyType」と「MyType」です)

しかし、C++で機能する同様のものがあります。これを確認してください c ++ concept:Compare

それは言います:

equiv(a、b)、! comp(a、b)&&!comp(b、a)と同等の式

5
xiaobing

他の回答に加えて、

コンパイラは、!=から==を推測することさえできません。

struct MyType
{
    int value;
};

bool operator == (const MyType& a, const MyType& b)
{
    return a.value == b.value;
}

int main()
{
    MyType a = {3};
    MyType b = {4};
    if (a != b)    // (* compilation Error *) 
        std::cout << "a does not equal b" << std::endl;
}

ただし、コンパイラに残りの合理的な演算子がクラスに適用されることを伝えるオプションがあればいいでしょう。

<utility>ヘッダーのいくつかの回答で説明されているように、そのような機能を提供できるものがあります。 mainの先頭に次の行を追加する必要があります。

using namespace std::rel_ops; 

ただし、このアプローチを使用するとコストがかかり、JDługoszが指摘しているように、あちこちでオーバーロードのあいまいさが発生します。

4
Shadi

次の例を考えてみましょう。

class point{

  unsigned int x;
  unsigned int y;

  public:
    bool operator <(const point& other){
      return (x+y) < (other.x+other.y);
    }

    bool operator == (const point& other){
      return (x==other.x) && (y==other.y);
    }
}

そして次に:

point a{1, 2};
point b{2, 1};

!(a <b)、!(b <a)だけでなく、!(a == b)も。

3
Andrew Kashpur

やや。
ただし、必要なのは boost :: operators

クラス型のオーバーロードされた演算子は、通常グループで発生します。 x + yを記述できる場合は、おそらくx + = yも記述できるようにする必要があります。 x <yと書くことができる場合、x> y、x> = y、およびx <= yも必要です。さらに、クラスに本当に驚くべき動作がない限り、これらの関連演算子の一部は他の演算子で定義できます(例:x> = y <=>!(x <y))。このボイラープレートを複数のクラスに複製するのは、退屈でエラーが発生しやすいものです。 boost/operators.hppテンプレートは、クラスで定義した他の演算子に基づいて名前空間スコープで演算子を生成するのに役立ちます。

2
Foo Is Bar

答えは明らかです。暗黙の方法はありません。 C++クラスを使用すると、演算子をオーバーロードできます。論理的に、if !(a < b) and !(b < a)それはa == bを意味するというあなたの考え。正しい。そして、以下のように演算子をオーバーロードできます。たとえば、Fractionクラス:

class Fraction {
    int num;
    int denom;
    . . .
    public:
    . . .
    bool operator < (const Fraction &other) {
        if ((this->num * other.denom) < (this->denom * other.num))
            return false;
        else
            return true;
       }
       bool operator == (const Fraction &other) (
           if (!(*this < other) && !(other < *this)) {
               return true;
           else
               return false;
       }
};
1
Prithwish Jana