【问题标题】:Could I use operator == if I only implemented operator <?如果我只实现运算符 <,我可以使用运算符 == 吗?
【发布时间】:2017-10-23 01:55:10
【问题描述】:

我已经为某个对象实现了operator&lt;。 从逻辑上讲,如果!(a &lt; b)!(b &lt; a) 表示a == b

这是自动推断的吗?如果我只实现&lt;,我可以使用==吗?

【问题讨论】:

  • @Titulum 用于用户定义类型,如果未定义运算符,编译器将引发错误。运算符没有默认行为,例如 == 会比较地址。
  • 如果你有 operator==operator&lt; 那么你可以用标准库来拥有其余的:std::rel_ops
  • 即使在数学中,您的逻辑也只适用于完全有序的集合,而不适用于 partially ordered sets
  • operator&lt; 甚至可能不会返回 bool(或允许 operator&amp;&amp; 的类型)。
  • 假设我有一种集合类型,我将“小于”定义为“是一个适当的子集”。所以 {1} 小于 {1, 2}。 {3} 不小于 {1, 2} 且 {1, 2} 不小于 {3},所以您的理论是 {3} == {1, 2} ?

标签: c++ operator-overloading


【解决方案1】:

C++ 无法自动推断这一点有几个原因:

  1. 没有必要将每个类型都与operator&lt; 进行比较,因此该类型不一定定义operator&lt;
    • 这意味着operator==不能自动定义为operator&lt;
  2. operator&lt; 不需要比较它的参数。程序员可以为他们的类型定义运算符,以便对他们的参数做几乎任何事情
    • 这意味着您关于 !(a &lt; b) &amp;&amp; !(b &lt; 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;
}

【讨论】:

  • 错误:重载的 'operator==' 必须至少有一个类或枚举类型的参数
  • 仅作记录 - 我知道这是一项简单的任务(实际上就像定义
  • @MSalters 在别处解释说“operator== 不能根据 operatorstackoverflow.com/questions/44129275/…
  • "operator&lt; 不需要比较它的参数" 严格来说,不,编译器不能强制执行。但做其他任何事情都会把最小惊喜原则扔出窗外,开出租车,把遗体冲进马桶。 不要这样做。
  • @jpmc26 我完全同意,但是有些人看到操作员并认为“嘿,在[在此处插入奇怪的情况] 看起来会很棒”。例如,运算符函数operator&lt;&lt;operator&gt;&gt; 在流的标准库中被重载。我个人不喜欢它,但人们已经习惯并接受它。
【解决方案2】:

它无法从&lt; 推断出==,因为并非所有类型都是有序的,例如std::complex。是2 + 3i &gt; 1 + 4i还是不是?

此外,即使在通常排序的类型中,您仍然无法从 &gt;&lt; 推断出相等性,例如 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

【讨论】:

  • 第一个语句在逻辑上是有缺陷的。 IF operator&lt; 已定义, 可用于自动定义operator==。你不能颠倒过来。 complex 可以有一个 operator== 的非自动定义,这与它的 operator&lt; 无关。
  • 这些是其他条件。我特别指出了第一句中的逻辑,它指出T==T 不能从T&lt;T 派生,因为U&lt;U 可能没有定义。如此明确表明存在缺陷:这是一个不正确的概括。
  • @MSalters complexunorderness 的一个例子,并不是我说它有 operator&lt;
  • @Blaisorblade 在某些情况下,这样做会导致意外(因此是危险的)行为,例如 Eric 的设置示例。这似乎有足够的理由不这样做。
【解决方案3】:

没有。此方法适用于称为totally ordered 的类似数字的对象。对于各种集合/类,没有人能保证这种关系。甚至没有人能保证operator &lt; 会比较一些东西。

所以== 就是==。你可以通过&lt; 实现==,但这并不适合所有人,C++ 标准也不会为你做。

【讨论】:

  • 你也可以有一个严格的弱顺序,并且“平等”意味着“无法比拟”。
  • 不管怎样,如果你有&lt;= 运算符,那么你甚至可以使用(a &lt;= b) &amp;&amp; (b &lt;= a) 定义部分有序集的相等性,对吧?
  • @yoniLavi,假设在复数类中,operator&lt;= 被定义为仅比较两个参数的大小。
  • @templatetypedef 是的,你可以。如果您想将相等定义为您的数据类型的“无法比拟”,欢迎您这样做。但是,编译器在没有被指示的情况下对所有类隐式地为您做出这个假设是不直观的。大多数人在写“等于”时实际上是指“等于”
  • @ThePhoton,好吧,我想这与滥用符号一样合理,因为只有数量级很重要。我希望开发人员随后会以通常的方式明确定义相等,或者确实更喜欢根据大小推断相等运算符。两者都可能有意义,具体取决于用例。
【解决方案4】:

从逻辑上讲,如果 !(a

用数学术语表达其他人所说的:假设您有一个返回 booloperator &lt; 并定义了一个严格的弱顺序,并且您将 operator == 实现为返回 @987654326 @,那么这个操作符定义了一个与给定的严格弱序一致的等价关系。然而,C++ 既不需要operator &lt; 定义严格的弱顺序,也不需要operator == 定义等价关系(尽管许多标准算法如sort 可能会隐式使用这些运算符并需要严格的弱顺序rsp.等价关系) .

如果您想根据operator &lt; 的严格弱顺序定义所有其他关系运算符并与之保持一致,Boost.Operators 可能会为您节省一些输入。

因为很容易误用不符合标准算法要求的operator &lt;,例如由于不小心通过std::sortstd::lower_bound 等使用它,我建议将operator &lt; 定义为严格的弱顺序或根本不定义。 CodesInChaos 给出的例子是偏序,不满足严格弱序的“不可比性传递性”要求。因此,我建议用不同的名称来调用这种关系,例如bool setLess(const MySet &amp;, const MySet &amp;).

来源:

【讨论】:

    【解决方案5】:

    C++ 不会自动推断这一点。对于operator&gt;operator&lt;=operator&gt;=,您可以使用std::rel_ops;这只需要operator&lt;。但是,它不提供operator== 方面的operator&lt;。你可以自己这样做:

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

    请注意:!((lhs &lt; rhs) or (rhs &lt; lhs))!(lhs &lt; rhs) and !(rhs &lt; lhs) 在数学上是等价的。

    【讨论】:

      【解决方案6】:

      编译器不会从&lt; 推断出==

      您可以通过一个简单的示例进行检查:

      #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) {
      

      【讨论】:

        【解决方案7】:

        正如许多人所说,不,你不能,编译器也不应该。

        这并不意味着从&lt;== 以及整个无数的变化不应该容易

        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);
            }
          };
        }
        

        以上内容只需编写一次,或从#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";
        }
        

        Live example.

        所有这一切的重点在于,作为一门语言,C++ 并没有假定&lt; 意味着&gt;&gt;=== 都有意义。但是您可以编写一个库,让您采用定义了&lt; 的类型,并添加一个简单的基类突然使所有这些其他操作都以零运行时成本定义。

        【讨论】:

        • boost::operators 非常适合这个 - 而且它不依赖于许多其他的 boost。但是我认为这个答案的设置方式使它看起来比实际更难使用 - 第一个代码块已经通过 boost 为你完成 - 但答案并没有说清楚 - 并且给出了数字这里的答案之一可能不会花时间去理解 - 这很遗憾,因为它是一个很好的解决方案。
        • @rox 澄清。
        【解决方案8】:

        std::rel_ops 命名空间中定义了一些模板,这些模板会自动定义缺失的运算符。

        它没有根据你的意愿定义基于 less 运算符的相等运算符。

        这还是很有用的;如果定义了 less 运算符和相等运算符,您将免费获得其他比较运算符。

        【讨论】:

          【解决方案9】:

          答案是否定的,你只需要一个简单的测试

          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: error: no match for ‘operator==’(操作数类型是 ‘MyType’ 和 ‘MyType’)

          但是在 C++ 中也有类似的东西,请查看c++ concepts:Compare

          上面写着:

          equiv(a, b),等价于 !comp(a, b) && !comp(b, a) 的表达式

          【讨论】:

            【解决方案10】:

            除了其他答案,

            编译器甚至无法从== 推断出!=

            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;
            }
            

            如果有一个选项可以告诉编译器其余的理性运算符适用于您的类,那就太好了。

            正如&lt;utility&gt; 标头中的某些答案所解释的那样,有一些东西可以提供这种功能。您需要在main 的开头添加以下行:

            using namespace std::rel_ops; 
            

            但是,正如 JDługosz 所指出的,使用这种方法成本高昂,并且会导致整个地方的过载模糊。

            【讨论】:

            • using 将导致整个地方的过载歧义。这就是为什么它们不再使用并且从未获得牵引力的原因,也是 Boost 想出另一种方法的原因。
            【解决方案11】:

            考虑以下示例:

            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

            【讨论】:

            • 好吧,如果它们不一样,这是否意味着编译器开发人员可能不会也不会隐式地用另一个替换一个?
            【解决方案12】:

            有点。
            但是你需要boost::operators

            类类型的重载运算符通常以组的形式出现。如果你 可以写 x + y,您可能还希望能够写 x += y。如果 你可以写 x y、x >= y 和 x = y !(x boost/operators.hpp 模板帮助 通过基于其他在命名空间范围内为您生成运算符 您在类中定义的运算符。

            【讨论】:

              【解决方案13】:

              答案很明确。没有隐含的方法。 C++ 类允许运算符重载。所以,你的想法从逻辑上讲,if !(a &lt; b) and !(b &lt; 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;
                     }
              };
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2019-05-15
                • 2019-05-13
                • 2021-09-11
                • 2023-03-15
                • 1970-01-01
                • 1970-01-01
                • 2012-12-19
                相关资源
                最近更新 更多