15

Comparing pointers to unrelated objects has unspecified results.

That would seem to suggest that this program may have undefined behaviour, at the very least because we cannot guarantee a strict weak ordering on the key type:

#include <map>

int main()
{
    int x = 0, y = 1;
    bool arbitrary = false;

    std::map<int*, bool> m{
       {&x, arbitrary},
       {&y, arbitrary}
    };
}

More generally, then, can we say that a map with pointer keys is a dangerous* proposition? Or is there some special rule we can lean on here?

* Academically speaking, that is; in reality I'm not aware of mainstream implementations that will actually raise hell over comparing arbitrary pointers.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055

4 Answers4

18

Yes, because std::map default comparison operator is std::less, which, unlike the standard comparison operator, is completely defined for pointer types.

[comparisons#2]

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]).

The implementation-defined strict total order over pointers is defined in [defns.order.ptr] as:

implementation-defined strict total ordering over all pointer values such that the ordering is consistent with the partial order imposed by the builtin operators <, >, <=, >=, and <=>

Holt
  • 36,600
  • 7
  • 92
  • 139
  • Oh yeah haha. Nice one – Lightness Races in Orbit Jan 05 '20 at 18:49
  • Interesting, that directly violates [\[expr.rel\]/4](http://eel.is/c++draft/expr.rel#4), no? – rustyx Jan 06 '20 at 13:41
  • @rustyx As I understand this, the standard imposes implementation to have a strict total order over all pointers, but this order is not presented through built-in operators but only through `std::less`-like functors. So there are two different "orders" (one implementation-specific usable through `std::less`, and one for built-in operators), and they have to be consistent when both are defined (same-array / members of same object). – Holt Jan 06 '20 at 13:50
  • But it says "*order imposed by the **builtin operators < ...***" ;) – rustyx Jan 06 '20 at 13:58
  • @rustyx The *"**implementation**-defined strict total order over pointers"* must be consistent with the standard-defined **partial** order imposed by the builtin operators. – Holt Jan 06 '20 at 13:59
  • @rustyx A valid implementation is allowed to produce `false` for **both** `p1 < p2` and `p2 < p1` when `p1` and `p2` do not fall into the scope defined by [expr.rel]/4, but **at least one of** `std::less{}(p1, p2)` or `std::less{}(p2, p1)` must be `true`. – Holt Jan 06 '20 at 14:03
  • @L.F. According to http://eel.is/c++draft/comparisons#2, even the `void`-specialization should provide a strict total order. – Holt Jan 08 '20 at 10:02
  • @Holt It's actually [LWG2450](https://wg21.link/LWG2450), which was resolved as of C++17. I gotta learn more ... – L. F. Jan 08 '20 at 10:22
8

std::less (default comparer of std::map) has special treatment about pointer allowing that:

A specialization of std::less for any pointer type yields a strict total order, even if the built-in operator< does not.

And about

can we say that a map with pointer keys is a dangerous proposition?

So it is fine in general.

Additional precaution should be taken with const char* key:

We compare pointers and not string content (mostly beginner confusions).

C-string literals with same content have no guaranty to be equals:

"literal" == "literal"; // Not guaranteed
"literal" < "literal"; // false .. or true
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 1
    OTOH in a scope where a static variable is guaranteed to exist in one instance in the program, every source code instance of a literal string is guaranteed to have one well defined address, so `inline void *literal() { return "literal";}` guarantees that `literal()==literal()`. OTOH the same literal string in diff file static functions in diff TU have no such guarantee. – curiousguy Jan 05 '20 at 19:16
2

std::map use std::less that have a specialization for pointer type :

A specialization of std::less for any pointer type yields a strict total order, even if the built-in operator< does not. The strict total order is consistent among specializations of std::less, std::greater, std::less_equal, and std::greater_equal for that pointer type, and is also consistent with the partial order imposed by the corresponding built-in operators (<, >, <= and >=).

For a more specific description i leave you 2 links:

std::less first link

std::less second link

Zig Razor
  • 3,381
  • 2
  • 15
  • 35
1

that is; in reality I'm not aware of mainstream implementations that will actually raise hell over comparing arbitrary pointers.

Depends on what hell means. Hell isn't just crashing right away or scrambling memory with random data. It could be giving indeterminate results to what should be a deterministic function.

GCC was known to optimize pointer comparisons on objects known to point to related (virtual) (sub) objects of different complete objects A and B: anything based on &A would compare differently to anything based on &B, even &A+1, even when the address were actually the same. I don't know if that was observed in C or in C++, but your question is pretty much C/C++ (and applies to qsort too).

That is, the result of a pointer comparison would depend on the known origin of a pointer. That mean that depending on optimization level, inlining context, translation unit, etc. a comparison could give different results.

So yes it could break down on at least some versions of a popular compiler if you did these pointer comparisons.

Which you don't as std::map is not defined in term operator< but in term of std::less.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • Granted; "raise hell" I suppose is left-over wording from an earlier revision of the question when I thought the pointer comparison itself might have UB; a broken ordering still could but that's not what the quoted bit says. I should have updated that too. But if I had, I wouldn't have received this helpful addition :) – Lightness Races in Orbit Jan 05 '20 at 19:06