Here is my lightweight solution that involves no operations at run time:
template<typename T>
struct type { static void id() { } };
template<typename T>
size_t type_id() { return reinterpret_cast<size_t>(&type<T>::id); }
struct A { };
int main ()
{
cout << type_id<int>() << " " << type_id<int*>() << " "
<< type_id<A>() << " " << type_id<int>() << endl;
}
The type id of type T
is nothing but the address of function type<T>::id
, re-interpreted as a number. Being a static method, there is a unique such function per type. These addresses are assigned by the linker (I think), so they remain constant between different runs. The overhead in executable size is linear in the number of type ids requested (that is, the number of instantiations of type<T>
).
This solution has been tested in gcc and clang, and works correctly across different compilation units, i.e. you get the same unique type id for the same type in different compilation units. However, I cannot quite explain why/how this happens.
type_id()
is easily inlined, so has no run-time cost. However, since it contains reinterpret_cast
(and since the function address is not known to the compiler), it cannot be made constexpr
. But I don't see this as a problem: if you want to use something e.g. as a template argument, you can use the type directly instead of its id. The id is for run-time use only.
My original implementation is using const void*
as the return type of type_id()
, so I know it has the same size as any pointer. I adapted it to size_t
to fit the question. I think this is still safe, but I can't see why const void*
wouldn't be fair enough.
By the way, because I haven't used it very much, I'd be happy to hear if there may be problems with this approach.
type_index
. (Yours is a standard problem with a standard solution from the standard library, which is this one.) \$\endgroup\$