New feature of C++20 - "spaceship" operator

The three-way operator was proposed by Herb Sutter and officially included in the C++20 draft determined on October 8, 2019. After using three-way operators in the class, the compiler can generate six basic operators by default. The use of this new feature reduces the workload of development to a certain extent. Therefore, it is also loved by everyone and is called spaceship operator.

1 form of three-way operator

The three-way operator is different from the basic operator in 6. The C + + Standards Committee uses "< = >" as the operator of the three-way operator. The expression is as follows:

Left operand < = > right operand

The three-way operator returns an object, as shown below:

  • If left operand < right operand, (a < = > b) < 0
  • If left operand > right operand, (a < = > b) > 0
  • If the left and right operands are equal / equivalent, (a < = > b) = = 0

The return result of the three-way operator is the same as the strcmp and strncmp we actually use, but there are essential differences. The three-way operator returns std::strong_ordering,std::weak_ordering and STD:: partial_ The ordering object, while strcmp and strncmp return integer data.

2 default comparison

During programming, the class type can generate default comparison. After it is defined in the class, the compiler will generate codes of 6 comparison operators by default. The forms of generating default comparison are as follows:

//Class member function definition
 Return type class name::operator operator( const Class name & ) const &(Optional) = default;
//Nonmember function
friend Return type operator operator( const Class name &, const Class name & ) = default;
//Nonmember functions, passing by value
friend Return type operator operator( Class name, Class name ) = default;

When using the three-way operator, it has its own restrictions, that is, the returned result must be auto or std::strong_ordering,std::weak_ordering and STD:: partial_ Any one of the ordering.

The default comparison code is shown below:

class Point {
  int x;
  int y;
public:
  Point(int _x,int _y):x(_x),y(_y){};
  //The default comparison function is pre generated, and the compiler will generate <, = =, <, < =, > == Operation code
  auto operator<=>(const Point&) const = default;
};
int main() {
  Point pt1{1, 1}, pt2{1, 2};
  std::set<Point> s; // ok
  s.insert(pt1);     // ok
  std::cout << std::boolalpha
    << (pt1 == pt2) << ' ' // falseļ¼› operator = = implicitly adopts the default version
    << (pt1 != pt2) << ' ' // true
    << (pt1 <  pt2) << ' ' // true
    << (pt1 <= pt2) << ' ' // true
    << (pt1 >  pt2) << ' ' // false
    << (pt1 >= pt2) << ' ';// false
}

The running result of the code is:

false true true true false false

3 user defined comparison

In actual coding, we need to define three-way operator comparison in some scenarios. The compiler can generate corresponding comparison operator code according to our definition rules. The returned results can be divided into three types:

  • Strong order: std::strong_ordering
  • Weak order: std::weak_ordering
  • Partial order: std::partial_ordering.

3.1 strong order

When the returned objects are in strong order, you need to compare each object. The price comparison order can be defined according to your needs. As in the following example, strong order is used as the return object.

struct TotallyOrdered {
  std::string tax_id;
  std::string first_name;
  std::string last_name;
public:
 // Customize the operator < = >, because we want to compare the first name (before comparing the last name):
 std::strong_ordering operator<=>(const TotallyOrdered& that) const {
   if (auto cmp = last_name <=> that.last_name; cmp != 0)
       return cmp;
   if (auto cmp = first_name <=> that.first_name; cmp != 0)
       return cmp;
   return tax_id <=> that.tax_id;
 }
 // ... non comparison function
};

int main() {
  TotallyOrdered to1{"b","c","d"}, to2{"b","d","c"};
  auto res = to1<=>to2;
  if(res >0)
    std::cout<<"to1>to2"<<std::endl;
  else if(res < 0)
    std::cout<<"to1<to2"<<std::endl;
  else
    std::cout<<"to1==to2"<<std::endl;
}

The operation result is:

to1>to2

3.2 weak order

The returned result is STD:: weak_ In order, the following code ignores case when generating string comparison. The code is as follows:

class CaseInsensitiveString {
  std::string strS;
  int is_same(const CaseInsensitiveString& rhs) const {
        return strcasecmp(strS.c_str(),rhs.strS.c_str());
    }
public:
  CaseInsensitiveString(std::string _str):strS(_str){};
  std::weak_ordering operator<=>(const CaseInsensitiveString& b) const {
      if(is_same(b)>0) return std::weak_ordering::greater;
      else if(is_same(b)<0) return std::weak_ordering::less;
      else return std::weak_ordering::equivalent;
  }
};
int main() {
  CaseInsensitiveString str1("CPP"), str2("cpp");
  auto res = str1<=>str2;
  if(res >0)
    std::cout<<"to1>to2"<<std::endl;
  else if(res < 0)
    std::cout<<"to1<to2"<<std::endl;
  else
    std::cout<<"to1==to2"<<std::endl;
}

The operation results are as follows:

to1==to2

3.3 partial order

Partial ordering is a sort that allows the comparison of values that cannot be compared (unordered), such as floating-point sorting including NaN values.

class CaseInsensitiveString {
  std::string strS;
  int is_same(const CaseInsensitiveString& rhs) const {
        return strcasecmp(strS.c_str(),rhs.strS.c_str());
    }
public:
  CaseInsensitiveString(std::string _str):strS(_str){};
 std::partial_ordering operator<=>(const CaseInsensitiveString& b) const {
      if(is_same(b)>0) return std::partial_ordering::greater;
      else if(is_same(b)<0) return std::partial_ordering::less;
      else return std::partial_ordering::equivalent;
  }
};

int main() {
  CaseInsensitiveString str1("CPP"), str2("cpp");
  if(std::is_eq(str1<=>str2)) std::cout<<"str1 is str2";
  if(std::is_neq(str1<=>str2)) std::cout<<" str1 is not str2";
  if(str1>str2) std::cout<<" str1>str2";
  if(str1<str2) std::cout<<" str1<str2";
}

The operation result is:

str1 is str2

Summary:

In the three-way operation, if you need to compare each member of the class, you can use the default comparison. If you don't need it, you can customize the generation as needed. There may be some differences in the understanding of partial order Xiaobian. I hope you can leave a comment here.

Posted by tomerg3 on Sat, 04 Dec 2021 22:04:39 -0800