Recall: auto - automatic type deduction, <=> - 3-way comparison

auto results = s1 <=> s2;

Q: How can we support <=> in our own classes?

struct Vec {
	int x, y;
	auto operator<=>(const vec &other) const {
		auto n = x <=> other.x;
		return (n == 0)? y<=>other.y: n;
	}
};
// Now we can say
Vec v1{1, 2}, v2{1, 3};
v1 <=> v2
// But we can also say
v1 <= v2 // etc..
// The 6 relational operatos: **<**, **<=**, **==**, **!=**, **>**, **>=
//** automatically rewritten in terms of **<=>**
eg. v1 <=> v2 -----> (v1<=> v2) <=0

6 operators for free! But you can also sometimes get operator for <=> free!

struct Vec {
	int x, y;
	auto operator<=>(const Vec &other) const = default;
// "default" does lexicographic ordering on fields of Vec.
// - equivalent to what we wrote before
	}
}

What might the default behaviour not be correct?

struct Node {
	int data;    // lex order on these fields would 
	Node *next;  // compare ptr values - not useful!
};

<aside> 🧠 Null ptr is object and cannot have method

</aside>

struct Node {
	int data;
	Node *next; // Note: works for non-empty lists
	auto operator<=>(const Node &other) const {
		auto n = data <=> other.data;
		if(n != 0) return n;
		if(!next &&(other.next)) return n; // n already equal
		if(!next) return std::strong-ordering::less;
		if(!other.next) return std::strong-ordering::greater;
		return *next <=> *other.next;
	}
};

<aside> 💡 Invariants(不変性) + Encapsulation(カプセル化)

struct Node {
	int data;
	Node *next;
	...
	~Node(){delete next;}
};
Node n {2, nullptr};
Node m {3, &n};

What happens when these go out of scope?

Class Node relies on an assumption for its proper operation: that next is either nullptr or was allocated by new.

We can’t gurantee this invariant - can’t trust the user to use Node property

ex. stack - invariant - last item pushed is first item popped - but not if the client can rearrange the underlying data

Hard to reason about programs if you can’t rely on invariants.

</aside>

<aside> 💡 To enforce invariants, we introduce encapsulation

struct Vec { // ↓ also public: default visibility
	Vec (int x, int y); //       = public
private: // can't be accessed outside of struct Vec
	int x, y;
public: // anyone can access
	Vec operator+(const Vec &other);
	...
};

In general: want private fields; only methods should be public Better to have default visibility = private

switching from struct to class… ↓

class Vec {
	int x, y;
public:
	Vec(int x, int y);
	Vec operator+(const Vec &other);
	...
};

Let’s fix our linked list class

// list.cc
export class list {  // externally, this is struct list::Node
	struct Node; // **private** nested class - only accessible within class list
	Node *theList = nullptr;
public:
	void addToFront(int n);
	int &ith (int i); // means we can do list.ith(4) = 7;
	~List();
...
};

// list-impl.cc
struct list::Node { // Nested class
	int data;
	Node *next;
	...
	~Node(){delete next;}
};

void List::addToFront(int n) {
	theList = new Node{n, theList};
}

int &list::ith(int i) {
	Node *cur = theList;
	for(int j = 0; j < i; ++j) cur = cur->next;
	return cur->data;
}

List::~List(){delete theList;}

Iterator - loop over something

Only List can create/manipulate Node objs now.

→ can gurantee the invariant that next is always either nullptr or allocated by new

<aside> 💡 Iterator Pattern

<aside> 💡 SE Topic: Design Patterns


class List {
	struct Node;
	Node *theList = nullptr;
public:
	class Iterator {
		Node *p;
	public:
		Iterator(Node *p): p{p}{}
		Iterator &operator++(){ p = p->next; return *this; }
		bool operator != (const Iterator other) const {
			return p! = other.p;
		}
		Int &operator*() { return p->data; }
	}
	Iterator begin(){ return Iterator{theList}; }
	Iterator end(){ return Iterator{nullptr}; }
};

</aside>

</aside>