5

I was confused about why can't compare pointers to member using binary operator<

class Point3d{
  protected:
      //..
 public:
     float x;
      static list<Point3d*> *freeList;
 public:
     float y;
     static const int chunkSize = 250;
 public:
    float z;

};
and a template:

template< class class_type, class data_type1, class data_type2 >

char* access_order(data_type1 class_type:: *mem1, data_type2 class_type:: *mem2)
{

    return
      mem1 < mem2 ?
         "member 1 accurs first":
         "member 2 accurs first";
}  

when I called the access_order like below:

access_order(&Point3d::z, &Point3d::y);

the g++ reported:

"invalid operands of types ‘float Point3d::*’ and ‘float Point3d::*’ to binary ‘operator<’"

Is there a way compare pointer to member, I mean the unequal comparison, and how?

Fly
  • 51
  • 2
  • for the same reason you shouldn't compare pointers using operator – BЈовић Oct 25 '12 at 10:01
  • @FrerichRaabe I don't think it's a duplicate, since he's asking about pointers to data members, and not pointer to member functions. (Also, the leading answer in the thread you cite is incorrect.) – James Kanze Oct 25 '12 at 10:19
  • @JamesKanze: Sorry, I misread - I thought the other question said "pointer-to-member". I agree that it's not a duplicate. Can I revert my close request here? – Frerich Raabe Oct 25 '12 at 10:22

6 Answers6

2

You can compare the addresses of the members of an object:

A a;
if (std::less<void*>()(&a.a, &a.b))
    std::cout << "a precedes b\n";
else
    std::cout << "a follows b\n";
Pete Becker
  • 74,985
  • 8
  • 76
  • 165
2

One of the best option - make a raw copy via std::memcpy, calculate hash and then use it for comparison (thanks @HolyBlackCat for comments). The function below calculates the hash for passed pointer-to-member (tested on modern C++ 17 compilers VS, GCC. CLang).

#include <cstring>
#include <string_view>
#include <functional>

template <typename TObject, typename TMember>
size_t HashMemberPtr(TMember TObject::* memberPtr)
{
    char buf[sizeof memberPtr];
    std::memcpy(&buf, &memberPtr, sizeof memberPtr);
    return std::hash<std::string_view>{}(std::string_view(buf, sizeof buf));
}

Unfortunately it's not compatible with std::hash<> as last requires only one template argument.

How to use:

struct CPoint3D
{
    float x;
    float y;
    float z;
};

int main()
{
    const size_t xHash = HashMemberPtr(&CPoint3D::x);

    assert(xHash == HashMemberPtr(&CPoint3D::x));
    assert(xHash != HashMemberPtr(&CPoint3D::y));
    assert(xHash != HashMemberPtr(&CPoint3D::z));

    return 0;
}
Pavel K.
  • 983
  • 9
  • 21
  • 1
    Using a union this way causes undefined behavior (even if most compiler won't do anything about it). A legal alternative is `std::memcpy` into a byte array, or `std::bit_cast`. – HolyBlackCat Dec 25 '21 at 09:47
  • @HolyBlackCat , thanks for pointing to alternatives, they also work and can be used. About undefined behavior, from my point of view, all these **three** approaches do the same - allow to access to raw representation of pointer-to-member. For sure, this representation can be different in different compilers and we can't use this data for any other purposes (e.g. as offset), but **only as hash**. – Pavel K. Dec 26 '21 at 16:37
  • In this specific case, in practice all three do the same thing, because at least GCC promises in its manual to accept this kind of UB, and other compilers seem to do the same thing. But in general, relying on the result of UB because you observed it to work on one compiler on a single machine is a bad idea. – HolyBlackCat Dec 26 '21 at 17:02
  • I've updated answer, removed suspicious transform, but still prefer to stay with `union` because option with `std::memcpy` not compiles on **CLang** and `std::bit_cast` requires C++ 20. – Pavel K. Dec 27 '21 at 10:31
  • Memcpy should compile everywhere. Can you show the code and the errors? – HolyBlackCat Dec 27 '21 at 10:44
  • I am apologizing, looks like there was just my mistake, no problems with CLang. I've updated answer, now the code is shorter and looks better, hope this will help someone. Thanks @HolyBlackCat! – Pavel K. Dec 27 '21 at 16:18
  • 1
    Note that you might get 'surprising' results if you are dealing with virtual functions: you can get the same hash for a virtual function in a base and a derived class. – hedayat Jan 10 '22 at 22:41
1

For the same reason you can't compare pointers in general. The only comparisons for order that are supported is if two data pointers point into the same array. Otherwise, the results of the comparison are unspecified; a compiler is not required to make the operators "work" in any reasonable way. (Ensuring a total order here would require extra computation on some architectures.) Since there's no case you can get specified results for a pointer to member, the standard doesn't allow them as arguments to the operators.

If you need total ordering, std::less et al. is guaranteed to provide it. Including, if I understand the standard correctly, member pointers. (Although providing a total ordering for pointer to member functions would probably be very expensive.) Even then, however, this ordering may be arbitrary; it would certainly not be required to reflect any ordering in memory.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
0

If the Point3d overloads < then deference them and do the compare,

return *mem1 < *mem2 ? "member 1 accurs first": "member 2 accurs first";

or change the signature

char* access_order(data_type1 class_type:: &mem1, data_type2 class_type:: &mem2) char* access_order(data_type1 class_type:: mem1, data_type2 class_type:: mem2)

Did you want to do the compare on the actual memory address?

AbdElRaheim
  • 1,384
  • 6
  • 8
0

Pointers to members do not point to some memory themselves. They are just labels. The only thing you can do with them is to convert them to reference to pointee value of the given object with operator .* or ->* or store in another pointer to member variable.

struct A
{
    int a;
    float b;
};
A a;
int A::* p2m = &A::a;
int A::* p2m2 = p2m;

int & realPointer = a.*p2m;

Note, that you only can compare pointers of the same type, so you can't compare pointer to A::a (int A::*) with pointer to A::b (float A::*)

Lol4t0
  • 12,444
  • 4
  • 29
  • 65
0

Pointer comparison seem to tempting everyone but they always lead to a non-portable or undefined behavior.

The easiest answer is that you should never do this. There is no computational problem that cannot be solved with classical approaches.

Don't get me wrong, I am aware that asking wrong questions might get interesting thoughts, or a build better understanding on how a language or even CPU works.

Florin C.
  • 593
  • 4
  • 13