When are two pointers comparable? - c++

There so many questions on comparing two pointers, but I found none on whether the two types are such that the pointers can be compared. Given
A* a;
B* b;
I want to know if expression a # b is valid, where # is one of ==,!=,<,<=,>,>= (I don't mind about nullptr_t or any other type that can be implicitly converted to a pointer). Is it when A, B are
equal?
equal except for cv-qualification?
in the same class hierarchy?
...?
I didn't find anything in std::type_traits. I could always do my own SFINAE test, but I am looking for the rules to apply them directly. I guess that will be easier for the compiler, right?
EDIT To clarify again: I am comparing pointers, not objects pointed to. I want to know in advance when a # b will give a compiler error, not what will be its value (true or false or unspecified).

C++ Standard
5.9 Relational operators
Pointers to objects or functions of the same type (after pointer conversions) can be compared,
with a result defined as follows:
— If two pointers p and q of the same type point to the same object or function, or both point one past
the end of the same array, or are both null, then p<=q and p>=q both yield true and p<q and p>q both
yield false.
— If two pointers p and q of the same type point to different objects that are not members of the same
object or elements of the same array or to different functions, or if only one of them is null, the results
of pq, p<=q, and p>=q are unspecified.
— If two pointers point to non-static data members of the same object, or to subobjects or array elements
of such members, recursively, the pointer to the later declared member compares greater provided the
two members have the same access control (Clause 11) and provided their class is not a union.
— If two pointers point to non-static data members of the same object with different access control
(Clause 11) the result is unspecified.
— If two pointers point to non-static data members of the same union object, they compare equal (after
conversion to void*, if necessary). If two pointers point to elements of the same array or one beyond
the end of the array, the pointer to the object with the higher subscript compares higher.
— Other pointer comparisons are unspecified.
§

Those comparison operators are always 'valid', in that you can always use them. But the results usually will not be meaningful or useful.
== and != will essentially tell you whether or not a and b refer to the same object. If a == b, then changing *a will affect *b and vice-versa.
>, <, >=, and <= will tell you where the memory addresses are, relative to one another. Usually this information will be irrelevant to the functionality of your program, and in most cases it will be unpredictable. However, one example that comes to mind where you might be able to use it is if you know that a and b point to member of the same array. In which case a < b will tell you whether or not the object *a comes before *b in the array.

Related

`std::complex<T>[n]` and `T[n*2]` type aliasing

Since C++11 std::complex<T>[n] is guaranteed to be aliasable as T[n*2], with well defined values. Which is exactly what one would expect for any mainstream architecture. Is this guarantee achievable with standard C++ for my own types, say struct vec3 { float x, y, z; } or is it only possible with special support from the compiler?
TL;DR: The compiler must inspect reinterpret_casts and figure out that (standard library) specializations of std::complex are involved. We cannot conformably mimic the semantics.
I think it's fairly clear that treating three distinct members as array elements is not going to work, since pointer arithmetic on pointers to them is extremely restricted (e.g. adding 1 yields a pointer past-the-end).
So let's assume vec3 contained an array of three ints instead.
Even then, the underlying reinterpret_cast<int*>(&v) you implicitly need (where v is a vec3) does not leave you with a pointer to the first element. See the exhaustive requirements on pointer-interconvertibility:
Two objects a and b are pointer-interconvertible if:
they are the same object, or
one is a standard-layout union object and the other is a non-static data member of that object ([class.union]), or
one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no
non-static data members, the first base class subobject of that object
([class.mem]), or
there exists an object c such that a and c are pointer-interconvertible, and c and b are
pointer-interconvertible.
If two objects are pointer-interconvertible, then they have the same
address, and it is possible to obtain a pointer to one from a pointer
to the other via a reinterpret_­cast. [ Note: An array object
and its first element are not pointer-interconvertible, even though
they have the same address.  — end note ]
That's quite unequivocal; while we can get a pointer to the array (being the first member), and while pointer-interconvertibility is transitive, we cannot obtain a pointer to its first element.
And finally, even if you managed to obtain a pointer to the first element of your member array, if you had an array of vec3s, you cannot traverse all the member arrays using simple pointer increments, since we get pointers past-the-end of the arrays in between. launder doesn't solve this problem either, because the objects that the pointers are associated with don't share any storage (cf [ptr.launder] for specifics).
It's only possible with special support from the compiler, mostly.
Unions don't get you there because the common approach actually has undefined behaviour, although there are exceptions for layout-compatible initial sequences, and you may inspect an object through an unsigned char* as a special case. That's it, though.
Interestingly, unless we assume a broad and useless meaning of "below", the standard is technically contradictory in this regard:
[C++14: 5.2.10/1]: [..] Conversions that can be performed explicitly using reinterpret_cast are listed below. No other conversion can be performed explicitly using reinterpret_cast.
The case for complex<T> is then not mentioned. Finally the rule you're referring to is introduced much, much later, in [C++14: 26.4/4].
I think it would work for a single vec3 if your type contained float x[3] instead, and you ensure sizeof(vec3) == 3*sizeof(float) && is_standard_layout_v<vec3>. Given those conditions, the standard guarantees that the first member is at zero offset so the address of the first float is the address of the object, and you can perform array arithmetic to get the other elements in the array:
struct vec3 { float x[3]; } v = { };
float* x = reinterpret_cast<float*>(&v); // points to first float
assert(x == v.x);
assert(&x[0] == &v.x[0]);
assert(&x[1] == &v.x[1]);
assert(&x[2] == &v.x[2]);
What you can't do is treat an array of vec3 as an array of floats three times the length. Array arithmetic on the array inside each vec3 won't allow you to access the array inside the next vec3. CWG 2182 is relevant here.

Using std::less with nullptr

Does the assertion in the following code snippet always hold?
std::less<Object *> lessPtr;
Object * o = new Object();
assert(lessPtr (o, nullptr) == false);
Introduction
This question really boils down to whether the use of the less-than relational operator on pointer types where one operand is a nullptr will yield the "expected" result; which sadly isn't the case.
The result is unspecified.
Note: Do mind that std::less guarantees a total order; meaning that even if the result, when using the function object, is unspecified, it must yield the same unspecified value on each invocation.
What does the International Standard (N3337) say?
5.9p2 Relational operators [expr.rel]
Pointers to objects or functions of the same type (after pointer conversions) can be compared, with a result defined as follows:
If two pointers p and q of the same type point to the same object or function, or both point one past the end of the same array, or are both null, then p<=q and p>=q both yield true and p<q and p>q both yield false.
If two pointers p and q of the same type point to different objects that are not members of the same object or elements of the same array or to different functions, or if only one of them is null, the results of p<q, p>q, p<=q, and p>=q are unspecified.
If two pointers point to non-static data members of the same object, or to subobjects or array elements of such members, recursively, the pointer to the later declared member compares greater provided the two members have the same access control (Clause 11) and provided their class is not a union.
If two pointers point to non-static data members of the same object with different access control (Clause 11) the result is unspecified.
If two pointers point to non-static data members of the same union object, they compare equal (after conversion to void*, if necessary). If two pointers point to elements of the same array or one beyond the end of the array, the pointer to the object with the higher subscript compares higher.
Other pointer comparisons are unspecified.
20.8.5p8 Comparison [comparision]
For templates greater, less, greater_equal, and less_equal, the specializations for any pointer type yield a total order, even if the built-in operators <, >, <=, >= do not.
So, what is the standard really saying?
T * p = new T;
T * q = nullptr;
What is the verdict for p < q?
Since p and q don't point to different elements of the same array (including the element one past the last element of an array), and both don't point to non-static data members of the same object; the result when doing p < q (and p > q) is unspecified.
bool a = p < q; // unspecified
bool b = p < q; // unspecified
assert (a == b); // can fire
What about std::less?
However, when using std::less we are guaranteed a total order - which effectively means that the below assertion cannot fire (standard-20.8.5p8).
std::less<T*> comp;
bool a = comp (p, q); // unspecified
bool b = comp (p, q); // unspecified
assert (a == b); // can not fire
No, the ordering of a null pointer relative to any non-null pointer is unspecified.
The result of the comparision operators is unspecified if the operands "point to different objects that are not members of the same object or elements of the same array or to different functions, or if only one of them is null".
std::less and friends extend this to specify that there's a total order, but don't specify where null pointers occur in that order. So it's guaranteed that null will consistently be either greater than, or less than, any given non-null pointer. But it's not specified to be either less than, or greater than, all non-null pointers.

Is it safe to compare pointers of same type?

char** buffer{ /* some buffer */ };
char* ptr1{buffer[0]};
char* ptr2{buffer[10]};
assert(ptr1 < ptr2);
If two pointers point to different locations in the same buffer, is it safe to compare them?
I want to know if a range of pointers is valid by comparing: assert(rangeBeginPtr < rangeEndPtr).
You can compare pointers with the relational operators (<, >, <= and >=) provided they both point to an element of the same array, or one past that array. Anything else is unspecified behaviour as per C++11 5.9 Relational operators. So, given:
char xyzzy[10];
char plugh[10];
All these are specified to function correctly:
assert(&(xyzzy[1]) < &(xyzzy[4]));
assert(&(xyzzy[9]) < &(xyzzy[10])); // even though [10] isn't there.
but these are not:
assert(&(xyzzy[1]) < &(xyzzy[15]));
assert(&(xyzzy[9]) < &(plugh[3]));
The type doesn't come into it except that it has to be the same type if you're comparing two elements in the same array. If you have two char * variables, that's unspecified if they point to different arrays even though they have the same type.
You can determine the order of pointers only with on array object and if they are non-void. However, within one array object the comparison is well defined. The relevant clause in the standard is 5.9 [expr.rel] paragraph 2:
[...] Pointers to objects or functions of the same type (after pointer conversions) can be compared, with a result defined as follows:
If two pointers p and q of the same type point to the same object or function, or both point one past the end of the same array, or are both null, then p<=q and p>=q both yield true and p<q and p>q both yield false.
If two pointers p and q of the same type point to different objects that are not members of the same object or elements of the same array or to different functions, or if only one of them is null, the results of p<q, p>q, p<=q, and p>=q are unspecified.
If two pointers point to non-static data members of the same object, or to subobjects or array elements of such members, recursively, the pointer to the later declared member compares greater provided the two members have the same access control (Clause 11) and provided their class is not a union.
If two pointers point to non-static data members of the same object with different access control (Clause 11) the result is unspecified.
If two pointers point to non-static data members of the same union object, they compare equal (after conversion to void*, if necessary). If two pointers point to elements of the same array or one beyond the end of the array, the pointer to the object with the higher subscript compares higher.
Other pointer comparisons are unspecified.
== and != are valid and well-defined for all pointers of the same type. <, <=, >, and >= are only meaningful for pointers that point to objects in the same array or one-past-the-end of the array. They are also meaningful for pointers to sub-objects of a class object if the sub-objects have the same type and the same access specifier. If those conditions aren't met, the result is unspecified; one immediate consequence is that a<b and b<c does not imply that a<c, so you cannot use <, etc. as the comparator for a sort function.
std::less, std::less_equal, std::greater, and std::greater_equal for pointer types all define a total ordering; they can be used for sorting.

Is comparing two void pointers to different objects defined in C++?

Inspired by this answer about dynamic cast to void*:
...
bool eqdc(B* b1, B *b2) {
return dynamic_cast<void*>(b1) == dynamic_cast<void*>(b2);
}
...
int main() {
DD *dd = new DD();
D1 *d1 = dynamic_cast<D1*>(dd);
D2 *d2 = dynamic_cast<D2*>(dd);
... eqdc(d1, d2) ...
I am wondering if it is fully defined behaviour in C++ (according to the 03 or 11 standard) to compare two void pointers for (in)equality that point to valid, but different objects.
More generally, but possibly not as relevant, is comparing (==or !=) two values of type void* always defined, or is it required that they hold a pointer to a valid object/memory area?
C says:
Two pointers compare equal if and only if both are null pointers, both are pointers to the
same object (including a pointer to an object and a subobject at its beginning) or function,
both are pointers to one past the last element of the same array object, or one is a pointer
to one past the end of one array object and the other is a pointer to the start of a different
array object that happens to immediately follow the first array object in the address
space.
C++ says:
Two pointers of the same type compare equal if
and only if they are both null, both point to the same function, or both represent the same address.
Hence it would mean that:
a)
it is fully defined behaviour in C++ (according to the 03 or 11 standard) to compare two void pointers for (in)equality that point to valid, but different objects.
So yes, in both C and C++. You can compare them and in this case they shall compare as true iff they point to the same object. That's simple.
b)
is comparing (==or !=) two values of type void* always defined, or is it required that they hold a pointer to a valid object/memory area?
Again, the comparison is well-defined (standard says "if and only if" so every comparison of two pointers is well-defined). But then...
C++ talks in terms of "address", so I think this means that the standard requires this to work "as we'd expect",
C, however, requires both the pointers to be either null, or point to an object or function, or one element past an array object. This, if my reading skills aren't off, means that if on a given platform you have two pointers with the same value, but not pointing to a valid object (e.g. misaligned), comparing them shall be well-defined and yield false.
This is surprising!
Indeed that's not how GCC works:
int main() {
void* a = (void*)1; // misaligned, can't point to a valid object
void* b = a;
printf((a == b) ? "equal" : "not equal");
return 0;
}
result:
equal
Maybe it's UB in C to have a pointer which isn't a null pointer and doesn't point to an object, subobject or one past the last object in an array? Hm... This was my guess, but then we have that:
An integer may be converted to anypointer type. Except as previously specified, the
result is implementation-defined, might not be correctly aligned, might not point to an
entity of the referenced type, and might be a trap representation.
So I can only interpret it that the above program is well-defined and the C standard expects it to print "not equal", while GCC doesn't really obey the standard but gives a more intuitive result.
C++11, 5.10/1:
Pointers of the same type (after pointer conversions) can be compared
for equality. Two pointers of the same type compare equal if and only
if they are both null, both point to the same function, or both
represent the same address
So yes, the specific comparison is OK.
In general it is undefined behavior to attempt to create a pointer value that isn't a valid address - for example using pointer arithmetic to go before the beginning or after the one-after-the-end of an array - let alone use them. The result of stuff like (void*)23 is implementation-defined, so barring specific permission from the implementation it is in effect undefined behavior to compare those too, since the implementation might define that the result is a trap value of void*.

How do the operators < and > work with pointers?

Just for fun, I had a std::list of const char*, each element pointing to a null-terminated text string, and ran a std::list::sort() on it. As it happens, it sort of (no pun intended) did not sort the strings. Considering that it was working on pointers, that makes sense.
According to the documentation of std::list::sort(), it (by default) uses the operator < between the elements to compare.
Forgetting about the list for a moment, my actual question is: How do these (>, <, >=, <=) operators work on pointers in C++ and C? Do they simply compare the actual memory addresses?
char* p1 = (char*) 0xDAB0BC47;
char* p2 = (char*) 0xBABEC475;
e.g. on a 32-bit, little-endian system, p1 > p2 because 0xDAB0BC47 > 0xBABEC475?
Testing seems to confirm this, but I thought it'd be good to put it on StackOverflow for future reference. C and C++ both do some weird things to pointers, so you never really know...
In C++, you can't compare just any pointers using the relational operators. You can only compare two pointers that point to elements in the same array or two pointers that point to members of the same object. (You can also compare a pointer with itself, of course.)
You can, however, use std::less and the other relational comparison function objects to compare any two pointers. The results are implementation-defined, but it is guaranteed that there is a total ordering.
If you have a flat address space, it's likely that pointer comparisons just compare addresses as if they are integers.
(I believe the rules are the same in C, without the comparison function objects, but someone will have to confirm that; I'm not nearly as familiar with C as I am with C++.)
This is just a supplementation.
In C++ 20.3.3/8:
For templates greater, less,
greater_equal, and less_equal, the
specializations for any pointer type
yield a total order, even if the
built-in operators <, >, <=, >= do
not.
In C 6.5.8/5:
If two pointers to object or
incomplete types both point to the
same object, or both point one past
the last element of the same array
object, they compare equal. If the
objects pointed to are members of the
same aggregate object, pointers to
structure members declared later
compare greater than pointers to
members declared earlier in the
structure, and pointers to array
elements with larger subscript values
compare greater than pointers to
elements of the same array with lower
subscript values. All pointers to
members of the same union object
compare equal. If the expression P
points to an element of an array
object and the expression Q points to
the last element of the same array
object, the pointer expression Q+1
compares greater than P. In all other
cases, the behavior is undefined.
So, I think comparing char const* which belong to two different '\0'-terminated-string as in the question is an undefined behavior (in C).
Yes, they just compare memory address.