3

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.

  • On page 712: about function objects:

"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."

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

  • 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*>?

Maestro
  • 2,512
  • 9
  • 24
  • `operator <` is undefined when comparing unrelated pointers, however `std::less` is specified to be well-defined. I wonder why you are asking this question as you seem to know this. What is unclear exactly? – n. m. could be an AI Apr 25 '20 at 22:22
  • @n.'pronouns'm.: But `std::less` applies `<` on the arguments doesn't it? – Maestro Apr 25 '20 at 22:23
  • 2
    @Maestro - Not according to the C++ standard it doesn't. If a standard library implementation chooses to use `<` for it, then it's because it **knows the architecture** it will run on. The standard deals in what it can define for an abstract machine, which implementations translate to real machines. Since not **all** architectures support comparison with `<` of any two pointers, neither does the abstract machine. The `std::less` specialization is for implementations to do it by other available means. – StoryTeller - Unslander Monica Apr 25 '20 at 22:31
  • @StoryTeller-UnslanderMonica: Would you elaborate to add an answer? Thank you. – Maestro Apr 25 '20 at 22:33
  • 2
    *But std::less applies < on the arguments doesn't it?* No it doesn't. How could it? `<` is undefined, `std::less` is not, therefore `std::less` cannot call `<`. It is a logical impossibility. – n. m. could be an AI Apr 25 '20 at 22:38
  • 1
    note that 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 – 463035818_is_not_an_ai Apr 25 '20 at 22:38

3 Answers3

3

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.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
2

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.

JaMiT
  • 14,422
  • 4
  • 15
  • 31
  • So you mean if `nameTable`'s elements are initialized from the addresses of string objects which are elements from an array or another sequential container then using `<` is well-defined? e.g: `std::vector vstr{"Hello", "C++", "World!"}; std::vector nameTable; for(auto& s : vstr) nameTable.push_back(&s); std::sort(nameTable.begin(), nameTable.end(), []( string*& ps1, string*& ps2){return ps1 < ps2;}); // is it well-defined?` Am I right here? – Maestro Apr 25 '20 at 23:08
  • @Maestro Fragile, but well-defined in this limited case. – JaMiT Apr 25 '20 at 23:12
  • Ok get it now. Thank you! – Maestro Apr 25 '20 at 23:13
  • One last thing: `int* p = new int(1); int* p2 = new int(5); cout << less()(p, p2) << endl;` is always true? If so why compiling this snippet on GCC and Clang once without optimization and another with, give different result?!!! I am so sorry for wasting your time. – Maestro Apr 25 '20 at 23:56
  • @Maestro You already have your answer to that. Compiling that snippet on different compilers with different options give different results. Therefore, the result is not always true. – JaMiT Apr 26 '20 at 01:24
  • Ok but I always get the same result from using `<` and std::less ?! either they return true or false: `cout << (pi < pj); cout << less()(pi, pj);` always gives me the same result! So Why std::less is preferred? – Maestro Apr 26 '20 at 10:41
  • @Maestro Have you tried your test on different hardware? If you restrict yourself to personal computers, it is likely (but not guaranteed) that you will get the same result with that test. – JaMiT Apr 26 '20 at 23:00
  • I understand noe: If I need to compare 2 or more unrelated pointers then i should use std::less, greater... and not built-in is it right now?. – Maestro Apr 27 '20 at 00:44
0

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/

Jeffrey
  • 11,063
  • 1
  • 21
  • 42
  • I was certain that std::less did the same as operator< on the address, but @n.'pronouns'm says otherwise. Looking forward to their ;-) answer – Jeffrey Apr 25 '20 at 22:28
  • 1
    On a segmented architecture machine, you could sort 0x00112000, 0x00120002, 0x00120020, and the interesting thing is 0x00112000 and 0x00120020 are the same address; and 0x00120002 is actually a lower memory address than 0x00112000. There are some funky architectures out there. – Eljay Apr 25 '20 at 22:36
  • 2
    @Maestro You seem to be confusing the abstract model of the C++ standard with implementations of the model. The standard says that `std::less` always produces a strict total order, while `operator<` might not. It's up to the implementation to determine how `std::less` works. If the implementation has an `operator<` that always produces a strict total order, it can implement `std::less` using it, but it doens't have to. – eesiraed Apr 25 '20 at 22:48