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 *.