C++20新特性—三路比较符<=>

集合(set,map等)算法通常会涉及元素比大小,因此两个元素(类型)的比较操作就显得额外重要。两个类型之间的共有六种比较符(<,<=,>,>=,==,!=),如果需要完整的表达,就要写六个函数,如果需要与可转换类型比较,这项工作显得比较繁琐,因此这也是C++引入<=>的基本初衷。【如果认为用一个比较符,例如”<”就可以推导出另外几个,其实这只适用于一些场景,这也是C++引入新的比较操作符的另一个原因。】

<=>称为3-way comparison operator(三路比较符),它的定义如下:

a <=> b 返回一个类型,该类型在a < b,,a > b,a等效b三种情况下与0的比较分别是小于0,大于0,等于0。

上面的0仅是一个字面量,最好不要理解成int 0。按照其定义,原来两个类型之间的六种比较符(<,<=,>,>=,==,!=),都可采用<=>来表达,即:

a@b可表达为a<=>b @ 0或 0 @ b<=>a

其中@就是上面六种比较符,例如a<b可表达为a<=>b <0或 0 < b<=>a(编译器会尝试前者,不成功再尝试后者)。

上面定义中,<=>的返回值是一个对象(模板类),它的类型可以是:

  • std::strong_ordering
  • std::weak_ordering
  • std::partial_ordering

为理解它们的意义,先说明一下如果对一个集合中的元素进行排序,可能出现的情况:

  • 完全且严格顺序:所有元素都可比较,且有严格的顺序关系(可比较,等值唯一)
  • 完全且松散顺序:所有元素都可比较,但存在近似相等的情况(可比较,等值不唯一)
  • 不完全顺序:元素存在不可比较的情况(非全可比较,等值也不唯一)

如整数集合属于第一种情况,浮点数集合、大小写不区分的字符集合属于第二种情况,包含空值的字符集、包含NaN的数值属于第三种情况,很明显,这三种情况就分别对应std::strong_ordering,std::weak_ordering和std::partial_ordering。

标准库已定义了若干上述类型的静态对象,例如std::partial_ordering类型定义了4个:

  • std::partial_ordering::less
  • std::partial_orderingequivalent
  • std::partial_ordering::greater
  • std::partial_ordering::unordered

而std::strong_ordering定义了4个,std::weak_ordering定义了3个。这些静态对象就是操作符<=>的返回值,它们与字面量0的操作示例如下,结果与以前对<=>的定义是一致的,需要特别注意的是std::partial_ordering::unordered与0的比较都是false。

strong_ordering::less < 0     // true
strong_ordering::less == 0    // false
strong_ordering::less != 0    // true
strong_ordering::greater >= 0 // true

// unordered is a special value that isn't comparable against anything
partial_ordering::unordered < 0  // false
partial_ordering::unordered == 0 // false
partial_ordering::unordered > 0  // false

可以想象,strong_ordering类型是可以转换为相应的weak_ordering或partial_ordering类型,但反过来却不行。

在重载(自定义)类型的<=>时,会用到这些静态对象。

<=>应用的一个基本例子如下,其中三种类型的比较都应用了系统缺省的<=>实现(尽量利用系统缺省实现,这也是推荐做法),输出都是1(true)。

void test_compare() 
{
    struct ts {
      int i;
      char c;
      float f;
      auto operator<=>(const ts&) const = default;
    };
    
    int ia=1, ib=2;
    auto cmp1=ia<=>ib;
    std::cout<<(cmp1<0)<<std::endl;    
    
    std::vector<int> va={1,2,3};
    std::vector<int> vb={2,3};    
    auto cmp2=va<=>vb;
    std::cout<<(cmp2<0)<<std::endl;        
    
    ts ta = { 0, 'c', 1.0f};  
     ts tb={ 0, 'c', 1.0f};
   std::cout<< (ta==tb) <<std::endl;

}

下面是一个我们重载<=>的一个示例。

void test_compare() 
{
    struct ts {
      int id;
      float f;
      std::partial_ordering operator<=>(const ts& that) const
      {
          if(id<0 || that.id<0)  return std::partial_ordering::unordered;
          if(f>that.f) return std::partial_ordering::greater;
          if(f<that.f) return std::partial_ordering::less;          
          return std::partial_ordering::equivalent;
      }
      bool operator==(const ts& that) const
      {
          return (*this<=>that)==0;
      }            
    };
    
    ts ta = {2, 1.0f};  
    ts  tb={3, 1.0f};
    std::cout<< (ta==tb) <<std::endl;
    std::cout<< (ta>tb) <<std::endl;
}

上面示例中,只有在id大于0时才进行比较。当然这个示例没有什么实际意义。要注意的是,目前重载<=>后,>=,>,<=,<可自动调用<=>并做相应转换,==!=还需单独编写==,如上例所示,如果去掉==重载,则ta==tb不能编译通过。因此,有下表的关系(来自于https://brevzin.github.io/c++/2019/07/28/comparisons-cpp20):

 

Equality

Ordering

Primary

==

<=>

Secondary

!=

<, >, <=, >=

其中,Secondary行中的比较符可由对应primary行中的比较符隐式转换而来,也就是一个新类型的六种比较关系现在由两种比较来完成。

 

 

 

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值