Re: Is void* as key a bad idea?
 
On 2010-02-19, DeMarcus <use_my_alias_here@hotmail.com> wrote:
Alf P. Steinbach wrote:
      assert( pv1 == pv2 );    // Uh oh, not guaranteed.
  }
To some C++ programmers it comes as a surprise.
How can I solve this to always be safe? Would a base class solve everything?
Using virtual inheritance and a sprinkling of templates.
This is a problem of reducing an object to a unique ID.
A C++ class object can be observed through various views: namely through
a reference or pointer to any of its base classes.
There is no general way, other than being careful, to write an object
such that a call to a virtual function REF.func() will always
call the same function func.  In a  C++ class lattice, if two classes B1
and B2 appear on independent branches, not connected by inheritance
(e.g. siblings), and both define a virtual function f with the same
argument signature, then you get a different f if you call through a B1
reference  or B2 reference to the object. These are distinct functions
and do not override each other. The only way that the same f is obtained
is if the function f is overridden at another node in the class lattice,
which inherits from B1 and B2.
So we cannot simply use a ``virtual void *id()'' function
to solve this problem in a completely fool-proof way.
However, we can use a simple nonvirtual function placed into
a base class, and ensure that we always use virtual inheritance
for that class:
  class object_with_id {
  public:
    void *id() { return this; }
  };
  class myclass : virtual public object_with_id { ... };
So now even if two class writers independently inherit from
object_with_id (using virtual inheritance), and then, in turn, their
classes are combined togheter with inheritance, the resulting object
will have one copy of object_with_id, and thus just a single
object_with_id::id function which returns just one possible pointer.
Now how you might use this would be to to write a wrapper
template function called id:
  #include <cstdlib> // for abort
  // in general, we don't know whether a pointer serves as an ID
  // so to be safe, we abort.
  // (We could insert some kind of compile-time constraint
  // violation here which is only triggered at template expansion
  // time, to get a compile-time check).
  template <typename T>
  void *id(T *ptr)
  {
     abort();
  }
  // For objects which inherit from our object_id base,
  // we /can/ compute the id, with this specialization:
  template <>
  void *id(object_with_id *ptr)
  {
    return ptr->id();
  }
  // Objects of the basic type int have a straightforward id:
  template <>
  void *id(int *ptr)
  {
    return ptr;
  }
Now always use this id function, rather than just casting
the pointer to void *.
  template <typename T>
  void add_to_set(..., T *ptr)
  {
     .... insert(id(ptr));
  }
I would use the type char * instead of void * because
void * has type pitfalls. If you forget to use id(ptr),
and just write ptr, it will work.
The void * type is braindamaged and should be avoided;
use char *.
  // inside object_with_id
  char *id() { return static_cast<char *>(id); }
Use a std::set<char *> as your registry. Now you can't just accidentally
insert any pointer into your set, because pointers don't implicitly
convert into char * (unless they are already char *).
Now if you write  insert(ptr) instead of insert(id(ptr)), you get an
error when ptr is not of type char *; you are forced to use the proper
id function to reduce objects to their id of type char *.