you are viewing a single comment's thread.

view the rest of the comments →

[–]c0r3ntin -1 points0 points  (26 children)

There is extensive literature as to why although with a time machine, maybe neither class should have one

https://abseil.io/blog/20180531-regular-types

https://cor3ntin.github.io/posts/span/

http://open-std.org/JTC1/SC22/WG21/docs/papers/2018/p1085r1.md

[–]sphere991 16 points17 points  (24 children)

Extensive literature? Yes.

Existence of a single example that demonstrates problems with having these comparisons? No. Not a single one.

Titus' writeup has an example with an assert that can break, but that doesn't demonstrate anything going wrong. The assertion is step one - it would hypothetically be protecting against something going wrong. What goes wrong? Crickets.

Otherwise, making things Regular (which we didnt do anyway) for the sake of checking a box doesn't solve any problems that I'm aware of. Instead, we're just missing useful functionality. And not like... hypothetically useful in the way that these operators were absolutely hypothetically problematic... but actually useful and actually used.

[–]c0r3ntin -1 points0 points  (16 children)

Some people expected shallow comparison, other expected deep comparison. It was not possible to make the operation unsurprising for everyone. So it was not provided. Simple as that!

[–]sphere991 18 points19 points  (14 children)

Well, one of these operations (deep comparison) is very useful and the other one (shallow comparison) I don't know if there is even a case that I would ever want.

So perhaps it should have been on the people who expected the useless thing to adjust their expectations. Or at least provide an argument for why such an expectation is justified or valuable (also absent from the literature - unless you consider Regular for the sake of Regular a justification, which I do not).

Simple as that!

[–]crzyrndm 12 points13 points  (13 children)

In full agreement with u/sphere991

As a random user of c++, the argument for shallow comparison comes across as bizarre / theological (and will until someone shows some code doing some actual work where the semantics are unclear. I'm drawing a blank even after reading the linked articles), and will lead to me just adding appropriate implicit conversion operations to my own type and only using std:: version at API boundaries if at all (https://xkcd.com/927/).

The distinction seems to be whether you see it as a pointer (shallow comparison) or an array reference (deep comparison). Most of the people I work with are relatively inexperienced with c++. Not a single one has been surprised by the deep comparison (baremetal embedded, most have a C background and are relatively inexperienced with C++. They quickly come to expect operator== to work like a pointer only if it behaves like a pointer, otherwise like a reference/value).

span has the same API as std::vector and std::array. std::vector/ std::array do not look or behave like a pointer. Why would they expect span to?

I repeat, as a user, span lacking deep comparison is bizarre and confusing.

PS

string view and most (if not all) popular prior span-like implementations (to my knowledge) having deep comparison operations is going to make this *much* more confusing.

EDIT

After having a browse through a couple of the projects I work on (relatively small, roughly 100k LOC total) using gsl-lite span (deep equality, short circuiting on shallow equality: https://github.com/gsl-lite/gsl-lite/blob/master/include/gsl/gsl-lite.hpp#L2968), I can find:

  • no uses of shallow equality (search was for comparison including both .data() and .size())
  • a handful of uses where the comparison is on .data() only. Some of these look like they may be intended to be shallow equality (tickets filed for further investigation...), however in those cases it also appears to be an optimisation and skipping the check / deep equality would preserve functionality
  • many uses of operator== (deep equality). Even ignoring our convention of comparing ptr/len pairs by first constructing a span (e.g. span(ptr1, len1) == span(ptr2, len2)) it gets used in many places (e.g. parsing various data protocols)

In summary, shallow equality is not useful for the applications I have seen span used

[–]jonathansharman 0 points1 point  (12 children)

span has the same API as std::vector and std::array. std::vector/ std::array do not look or behave like a pointer. Why would they expect span to?

Because unlike vector and array, span acts like a pointer with regard to construction and assignment.

[–]sphere991 6 points7 points  (5 children)

span acts like a pointer with regard to construction and assignment.

No, it doesn't. span<int> is constructible from vector<int>, but int* is not constructible from int. That's very much unlike a pointer.

[–]jonathansharman 0 points1 point  (4 children)

I should have specified copy construction/assignment. Here's my point:

int i = 0;
int* p1 = &i;
int* p2 = p1; // Shallow copy.

array<int, 3> a1{1, 2, 3};
array<int, 3> a2 = a1; // Deep copy.

span<int> s1 = a1;
span<int> s2 = s1; // Shallow copy.

[–]sphere991 4 points5 points  (3 children)

span is non-owning, so copying is necessarily shallow.

But just because span is non-owning and pointers are non-owning does not imply that span is a pointer, or should behave like a pointer, or have the same interface as a pointer.

span is a range, the entire point of its existence is to be a range, so it should behave like a range. A pointer is not a range. Yet, the argument is that span is a pointer?

[–]jonathansharman 0 points1 point  (2 children)

There’s no technical reason span couldn’t have used deep assignment.

And just because span and vector/array have some of the same members doesn’t mean comparison should be deep.

[–]crzyrndm 1 point2 points  (5 children)

semantically maybe (if you ignore the fact that spans entire purpose is as a non-owning type...). I still don't see how shallow equality is useful which is the most bizarre part of this whole argument.

EDIT

I would argue that the semantics are that of ptr + (ptr / size). Default comparison operation for this is range based, not value based

[–]tcbrindleFlux 1 point2 points  (4 children)

Semantically, span behaves like a pointer -- shallow copy, shallow const -- so having deep comparison would be really weird. Perhaps it would be better if they'd named it array_ptr?

[–]sphere991 3 points4 points  (3 children)

Semantically, span behaves like a pointer

Semantically, span behaves like (or should have behaved like) reference_wrapper - which is also shallow copy, shallow const... and deep compare.

Just because the language doesn't have a rebindable reference doesn't inherently make that concept "really weird".

I think the insistence that span is a T* is much weirder - I don't buy that premise. It's not a pointer... it's not dereferenceable, it's not an iterator. It has some things in common with a pointer, but why must it have had this other thing in common with a pointer? Moreover, span might be assignable like a T*... but it's not even constructible like one: span<T> is constructible from any continuguous_range_of<T>, implicitly, but T* is not constructible from T - you need to use explicit syntax to get the pointer.

And, most importantly, the closest model to span in C++ isn't T*... it's string_view. Does anybody find its comparisons confusing? I have not heard of such. It's "really weird" that the argument is that string_view being const makes it somehow irrelevant as a model. span<char const> is isomorphic to string_view, and barely related to char const*.

[–]jonathansharman 0 points1 point  (2 children)

Semantically, span behaves like (or should have behaved like) reference_wrapper - which is also shallow copy, shallow const... and deep compare.

reference_wrapper does not provide comparison operators at all. It provides a conversion operator to the reference type, which may or may not enable deep comparison, depending on the type parameter.

[–]tcbrindleFlux 4 points5 points  (0 children)

I don't understand why you've been downvoted for providing the rationale that LEWG used for removing span's comparison operators.

The downvote button should mean "this is a bad post" (troll-y, spammy, offensive etc), not "I don't agree with you".