Maestro
Maestro

Reputation: 2572

C++ primer 5 ed. : using sort on a vector of pointers is it undefined?

In C++ primer 5 ed. page 172:

"We cannot use the relational operators on pointers to two unrelated objects:

int i = 0, sz = 42;
int *p = &i, *e = &sz;// undefined: p and e are unrelated; comparison is meaningless!    
while (p < e)  ".

But if I run the same code on GCC or CLANG it compiles fine even against wall flag.

"One important aspect of these library function objects is that the library guarantees that they will work for pointers. Recall that comparing two unrelated pointers is undefined (§ 3.5.3, p. 120). However, we might want to sort a vector of pointers based on their addresses in memory. Although it would be undefined for us to do so directly, we can do so through one of the library function objects:

vector<string *> nameTable;  // vector of pointers

// error: the pointers in nameTable are unrelated, so < is undefined

sort(nameTable.begin(), nameTable.end(),
 [](string *a, string *b) { return a < b; });

// ok: library guarantees that less on pointer types is well defined

sort(nameTable.begin(), nameTable.end(), less<string*>());

It is also worth noting that the associative containers use less<key_type> to order their elements. As a result, we can define a set of pointers or use a pointer as the key in a map without specifying less directly."

Upvotes: 3

Views: 369

Answers (3)

Jeffrey
Jeffrey

Reputation: 11430

This answer contains false information. I'm not deleting it, because the comments are very useful.

What the book mean is that it is not meaningful to compare the address of unrelated objects. You can write code that does it, but it is not-meaningful.

If you use a container that sorts its content with pointers, well, the order will be a consistent (within a single run) but not very useful order. If address of object A is smaller than object B it will stay smaller. So, it is stable, it will work. But it doesn't tell you anything about A or B.

And next run, A and B may switch places in memory and give you a different order.

Edit: refer to the other answers ;-)

Edit: fun cases I stumbled upon, googling the subject: https://quuxplusone.github.io/blog/2019/01/20/std-less-nightmare/

Upvotes: 0

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122994

I'd like to extend on my comment from above:

you do not necessarily need to know how the specialization of std::less for pointers actually works. The important thing is that it is there and does the right thing

I didn't want to imply that your question does not deserve an answer, but rather thats what the C++ standard specifies:

For templates less, greater, less_­equal, and greater_­equal, the specializations for any pointer type yield a result consistent with the implementation-defined strict total order over pointers ([defns.order.ptr]). [ ... Note ... ]

For template specializations less, greater, less_­equal, and greater_­equal, if the call operator calls a built-in operator comparing pointers, the call operator yields a result consistent with the implementation-defined strict total order over pointers.

The standard does not mention how specializations of std::less (or the other comparisons) for pointers are realized. It only specifies that their "call operator yields a result consistent with the implementation-defined strict total order over pointers." To do that implementations cannot always use the built-in < operator, because [expr.rel#4]:

The result of comparing unequal pointers to objects is defined in terms of a partial order consistent with the following rules:

(4.1) If two pointers point to different elements of the same array, or to subobjects thereof, the pointer to the element with the higher subscript is required to compare greater.

(4.2) If two pointers point to different non-static data members of the same object, or to subobjects of such members, recursively, the pointer to the later declared member is required to compare greater provided the two members have the same access control ([class.access]), neither member is a subobject of zero size, and their class is not a union.

(4.3) Otherwise, neither pointer is required to compare greater than the other.

That is: Pointers that are not pointers into the same array or pointers to subobjects of the same object cannot portably be compared with the built-in <.

I don't know how less<string*> library function object guarantees the comparison although it only applies < into the arguments of element type?!

It does not. Or rather, it does not necessarily. Your compiler knows if on the target architecture < can be used for arbitrary pointers, but in general that is not the case.

In his example nameTable; it is a vector of pointers-to-strings thus they are contiguous in memory so how could it be undefined when using < in sort but "well--defined" when using std::less<string*>?

Thats a small misunderstanding. Indeed std::vector does store its element in contiguous memory, but the pointers you store in the vector can point anywhere. Hence, they are neither pointers to elements of the same array, nor pointers to subobjects of the same object. Your argument would hold if you had a std::vector<string> and wanted to compare pointers to elements in that container, but that is not what you are doing here.

Upvotes: 3

JaMiT
JaMiT

Reputation: 17007

I don't know how less<string*> library function object guarantees the comparison although it only applies < into the arguments of element type?!

The standard does not specify "it only applies < into the arguments of element type". Your C++ library might do that, but other possibilities exist. The point is that if you switch to a different platform, the specialization of std:less for pointers might need to do more than just applying <. You are shielded from needing to know this detail; the standard headers for that architecture will take care of it for you as long as you use std::less.

Using std::less is portable; using < is not.

In his example nameTable; it is a vector of pointers-to-strings thus they are contiguous in memory so how could it be undefined when using < in sort but "well--defined" when using std::less<string*>?

The pointers being contiguous is irrelevant. What matters are the pointed-to objects. If you have an array of strings, let's call it array, and if each element of nameTable points to an element of array, then ordering via < is defined by the C++ standard. However, if the pointed-to strings are not all from a single array, then the ordering via < is not guaranteed to work. It might work, but you have no guarantee.

Upvotes: 2

Related Questions