Given the following:

template<typename T>
class A
{
public:
    static const unsigned int ID = ?;
};

I want ID to generate a unique compile time ID for every T. I've considered __COUNTER__ and the boost PP library but have been unsuccessful so far. How can I achieve this?

Edit: ID has to be usable as the case in a switch statement

Edit2: All the answers based on the address of a static method or member are incorrect. Although they do create a unique ID they are not resolved in compile time and therefore can not be used as the cases of a switch statement.


Solution 1:

This is sufficient assuming a standards conforming compiler (with respect to the one definition rule):

template<typename T>
class A
{
public:
    static char ID_storage;
    static const void * const ID;
};

template<typename T> char A<T>::ID_storage;
template<typename T> const void * const A<T>::ID= &A<T>::ID_storage;

From the C++ standard 3.2.5 One definition rule [basic.def.odr] (bold emphasis mine):

... If D is a template and is defined in more than one translation unit, then the last four requirements from the list above shall apply to names from the template’s enclosing scope used in the template definition (14.6.3), and also to dependent names at the point of instantiation (14.6.2). If the definitions of D satisfy all these requirements, then the program shall behave as if there were a single definition of D. If the definitions of D do not satisfy these requirements, then the behavior is undefined.

Solution 2:

What I usually use is this:

template<typename>
void type_id(){}

using type_id_t = void(*)();

Since every instantiation of the function has it's own address, you can use that address to identify types:

// Work at compile time
constexpr type_id_t int_id = type_id<int>;

// Work at runtime too
std::map<type_id_t, std::any> types;

types[type_id<int>] = 4;
types[type_id<std::string>] = "values"s

// Find values
auto it = types.find(type_id<int>);

if (it != types.end()) {
    // Found it!
}

Solution 3:

This seems to work OK for me:

template<typename T>
class Counted
{
  public:
  static int id()
  {
    static int v;
    return (int)&v;
  }
};

#include <iostream>

int main()
{
  std::cout<<"Counted<int>::id()="<<Counted<int>::id()<<std::endl;
  std::cout<<"Counted<char>::id()="<<Counted<char>::id()<<std::endl;

}

Solution 4:

It is possible to generate a compile time HASH from a string using the code from this answer.

If you can modify the template to include one extra integer and use a macro to declare the variable:

template<typename T, int ID> struct A
{
    static const int id = ID;
};

#define DECLARE_A(x) A<x, COMPILE_TIME_CRC32_STR(#x)>

Using this macro for the type declaration, the id member contains a hash of the type name. For example:

int main() 
{
    DECLARE_A(int) a;
    DECLARE_A(double) b;
    DECLARE_A(float) c;
    switch(a.id)
    {
    case DECLARE_A(int)::id:
        cout << "int" << endl;
        break;
    case DECLARE_A(double)::id:
        cout << "double" << endl;
        break;
    case DECLARE_A(float)::id:
        cout << "float" << endl;
        break;
    };
    return 0;
}

As the type name is converted to a string, any modification to the type name text results on a different id. For example:

static_assert(DECLARE_A(size_t)::id != DECLARE_A(std::size_t)::id, "");

Another drawback is due to the possibility for a hash collision to occur.