13

I've bumped into a problem yesterday, which I eventually distilled into the following minimal example.

#include <iostream>
#include <functional>

int main()
{
    int i=0, j=0;
    std::cout
        << (&i == &j)
        << std::less<int *>()(&i, &j)
        << std::less<int *>()(&j, &i)
        << std::endl;
}

This particular program, when compiled using MSVC 9.0 with optimizations enabled, outputs 000. This implies that

  1. the pointers are not equal, and
  2. neither of the pointers is ordered before the other according to std::less, implying that the two pointers are equal according to the total order imposed by std::less.

Is this behavior correct? Is the total order of std::less not required to be consistend with equality operator?

Is the following program allowed to output 1?

#include <iostream>
#include <set>

int main()
{
    int i=0, j=0;
    std::set<int *> s;
    s.insert(&i);
    s.insert(&j);
    std::cout << s.size() << std::endl;
}
skaffman
  • 398,947
  • 96
  • 818
  • 769
avakar
  • 32,009
  • 9
  • 68
  • 103

2 Answers2

11

Seems as we have a standard breach! Panic!

Following 20.3.3/8 (C++03) :

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.

It seems a situation where eager optimizations lead to improper code...

Edit: C++0x also holds this one under 20.8.5/8

Edit 2: Curiously, as an answer to the second question:

Following 5.10/1 C++03:

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

Something is wrong here... on many levels.

Kornel Kisielewicz
  • 55,802
  • 15
  • 111
  • 149
  • +1 Wow! I totally missed that part of the spec when I just looked over it. Thanks for catching that. – templatetypedef Mar 16 '11 at 07:45
  • +1, that stipulation was put in there exactly to allow pointers to be used as keys in sets and maps. It seems, however, that the requirement is underspecified. My interpretation of the standard is that the second program may in fact print `1`. (Although msvc correctly outputs `2`.) – avakar Mar 16 '11 at 07:56
  • @avakar, found also something on pointer equality... Can `i` and `j` represent the same address? – Kornel Kisielewicz Mar 16 '11 at 08:04
  • I just browsed through all words `address` in the standard, but I couldn't find that requirement which says that distinct objects may not have the same address (there are exceptions -- unions, base classes etc). I wonder where it ended up. – avakar Mar 16 '11 at 08:10
  • 1
    I find this code interesting, if the variables are not optimized away (and they shouldn't as their addresses are being used) they are separate entities. @avakar, the second program should never print 1, you have pointers that refer to distinct objects, @Kornel Kisielewicz, @avakar: the two objects cannot have the same address if they are allowed to have different values. If they had the same address, then `int *p = &i; *p = 10;` would modify *both* ints, which is wrong. – David Rodríguez - dribeas Mar 16 '11 at 08:33
  • @dribeas, if you do anything non-trivial with the objects, the optimization goes away so `int *p = &i; *p = 10;` will work. Regarding the second program; the two pointers *are* distinct (that's what &i != &j means), but the total order imposed by std::less clumps them together (and I'm arguing it has the right to do so according to the standard). And since std::set compares objects solely using std::less, I'm arguing that `1` is the correct result. What do you think? – avakar Mar 16 '11 at 08:39
  • @avakar: Kornel's quote states a *total* order. I don't think the standard defines "total order", but mathematics does, and the standard contrasts a weak ordering with a total ordering in 25.3/4. Part of the definition of a strict total order is that for all x,y, xy or x=y (http://en.wikipedia.org/wiki/Total_order). So MSVC is non-conforming here, since "clumping together" is precisely what "total" says you cannot do. Maybe they forgot their math classes when they implemented 20.3.3/8 ;-) – Steve Jessop Mar 16 '11 at 10:23
  • @Steve, "clumping together is precisely what "total" says you cannot do", please elaborate, I disagree. The fact that "strict total order is that for all x,y, xy or x=y" clearly allows "clumping" – avakar Mar 16 '11 at 10:47
  • To make it clearer, 25.3/4 only requires that < be irreflexive and transitive (the definition of strict partial ordering) and the induced equivalence be transitive. – avakar Mar 16 '11 at 10:50
  • @avakar: in the example you raise, you get the output `000`, therefore none of `less()(x,y)`, `less()(y,x)`, `x==y` is true. Therefore `less` is not a strict total order. 25.3/4 defines a strict *weak* order. That's what's required of any associative container comparator. 20.3.3/8 requires that `std::less` in particular must be a strict *total* order. You have demonstrated that on MSVC, `std::less` is not a strict total order. Therefore, it's non-conforming, regardless of the fact that it is a strict weak order and therefore can be used as an associative container comparator. – Steve Jessop Mar 16 '11 at 11:08
  • Or are you saying that you disagree with Wikipedia's definition of a strict total order? It matches the definition I was given at university (as far as I remember, anyway), so if you look for a more authoritative source, I'm sure you'll find one. The only difference between a weak order and a total order is that a weak order allows non-equal elements to be equivalent in the order (aka "clumping"), whereas a total order does not. A partial order has even lower requirements - it doesn't even require that `!(x – Steve Jessop Mar 16 '11 at 11:18
  • @Steve, I certainly do not disagree with Wikipedia's definition of a strict total order (I also have a CS degree) :). However, `x==y` is not in any way related to `std::less`. The equivalence relation induced by `less`, let's call it `equiv` (that's how it is called in 25.3) is defined as `equiv(x, y) = !less(a, b) && !less(b, a)`. It is a different beast than `==`. The two relations are not consistent and my whole point is that the standard does not require them to be (which is an oversight AFAIC). – avakar Mar 16 '11 at 11:21
  • @avakar: but that's precisely the difference between a strict *weak* order, and a strict *total* order. The definition of "weak" says that `equiv` is an equivalence relation (in contrast with a partial order, where it need not be). The definition of "total" says that `equiv` is equality. In the "trichotomy" referred to in the Wiki article, `=` is identity, not just `equiv`. And hence in C++, that brings `==` into it, since `==` tests equality of pointer values. If it just meant `equiv`, then a strict total order would be the same thing as a strict weak order, which is not the case. – Steve Jessop Mar 16 '11 at 11:24
  • Btw, I don't have a CS degree, I have a maths degree ;-p Anyway, going back to the question in the title, "Does std::less have to be consistent with the equality operator for pointer type?", my short answer is, "Yes. 20.3.3/8 says it's a total order, and the definition of a strict total order involves equality". If it had wanted to, 20.3.3/8 could have said that it's a weak order, and then MS's implementation would conform. But it doesn't say that, it says total. – Steve Jessop Mar 16 '11 at 11:27
0

No, the result is obviously not correct.

However, MSVC is known not to follow the "unique address" rules to the letter. For example, it merges template functions that happens to generate identical code. Then those different functions will also have the same address.

I guess that you example would work better if you actually did something to i and j, other that taking their address.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • It's not obvious to me. Can you cite the relevant paragraphs of the standard? – avakar Mar 16 '11 at 07:43
  • I dont have the standard here, but it is a basic property of different objects of the same type to have different addresses. That's how you tell them apart. – Bo Persson Mar 16 '11 at 07:47
  • For the record, yes, if I use the variables, the results will change. Note that msvc takes care to ensure that &i != &j (which is required by the standard). However, regarding `std::less`, the standard only requires that the functor impose a total order and is consistent with operator < (whose results are unspecified in this case). And of course an ordering in which all elements are equivalent is a total order. – avakar Mar 16 '11 at 07:48
  • Bo, I get that different objects must have different addresses (save for a few exceptions), but that's actually true here (&i != &j). – avakar Mar 16 '11 at 07:49