Alright, so what do we make of this table? Before we get into that, let’s digress into generic programming fundamentals and its most cherished concept: Regular . A type is Regular if it is:

Default constructible

Copyable / Moveable / Swappable

Equality Comparable (in Fundamentals of Generic Programming, Regular requires a Total Order but the Ranges TS just uses Equality Comparable — I am sticking with the Ranges TS definition)

Now, it’s insufficient to leave it at that. It’s not enough that a type meets the syntactic constraints that these terms imply (which are the only constraints that we can check, really). They also also come with semantic constraints. So it’s not enough for Equality Comparable that x == y compile and yield bool . This operation must also be an equivalence relation. On top of that, it must also be what the Ranges TS calls equality preserving which has the important property that:

Expressions required by this specification to be equality preserving are further required to be stable: two evaluations of such an expression with the same input objects must have equal outputs absent any explicit intervening modification of those input objects. [ Note: This requirement allows generic code to reason about the current values of objects based on knowledge of the prior values as observed via equality preserving expressions. It effectively forbids spontaneous changes to an object, changes to an object from another thread of execution, changes to an object as side effects of non-modifying expressions, and changes to an object as side effects of modifying a distinct object if those changes could be observable to a library function via an equality preserving expression that is required to be valid for that object. — end note ]

Fundamentals of Generic Programming describes the equivalence between copy construction, assignment, and equality as satisfying four axioms:

T a = b; assert(a == b); T a; a = b; is equivalent to T a = b; T a = c; T b = c; a = d; assert(b == c); T a = c; T b = c; zap(a); assert(b == c && a != b);

The first states the connection between copy construction and equality. The second states that default construction and assignment is equivalent to copy construction. The third states that modifying an irrelevant element can’t change the constructed relationship between two others (we copy constructed a and b from c and then changed a , this shouldn’t affect the relationship between b and c ). The fourth involves a function zap which changes the value of its operand — changing the value should affect its equality but not other, unrelatedobjects’ equality).

I italicized value in the last sentence because it’s an important concept: copy construction, copy assignment, and equality all are based on the value of the type. It’s important for the value to actually mean the same thing in all three cases.

The canonical example of a type that is Regular is int (hence the occasional saying, “do as the int s do”). It should be pretty straightforward to work through the axioms and convince yourself that they’re all satisfied. The other canonical example are pointers, T* . The value of a pointer is its address, all of the operations are based on this address, so we get Regular basically directly from int s being regular.

One example of a type that is not Regular, but just about meets all the requirements, is std::string_view . This is fundamentally because it has different notions of value for its copy construction and copy assignment (i.e. the address of a character and a size) than it does for its equality operator (i.e. the value of that buffer). And hence, we can construct an example that violates axiom (4):

Here we change the value of a to point to an entirely different string . We did, in a real sense, change the value of a . But while b == c still holds (indeed, there is nothing we could do to change that), a != b is false . string_view comparison is deep, and the underlying ranges have the same contents.

With span<int> , we could violate axiom (4) in two different ways. We could either change a to point to a different, equivalent buffer as above (that is, changing its “direct” value), or we could change a[0] to have a different value (that is, changing its “underlying” value, since this is a non-const view). Either way, we end up with a != b failing since either we’re now pointing at two equivalent ranges or we ended up modifying both with the single modification. That is, even though span<int> s can be compared with == , and even in a way that’s an equivalence relation, it’s still not enough to be considered Equality Comparable.

Another way of looking at this is the “distinct object” test. When I copy an object, do I end up with two distinct objects such that there is nothing I can do to one that changes the value of the other? If not, the type is not Regular.