'How can I get a consistent, unique, identifier for a unique class combination?

I need a way to identify a unique combination of template types that gives me an easily indexable identifier.

I have the following class:

#include <cstdint>

typedef std::uint32_t IDType;

template<class T>
class TypeIdGenerator
{
private:

    static IDType m_count;

public:

    template<class U>
    static IDType GetNewID()
    {
        static const IDType idCounter = m_count++;
        return idCounter;
    }
};

template<class T> IDType TypeIdGenerator<T>::m_count = 0;

Which works, but when utilized across dll and exe, each "instance" in each process resets the count, so if in the .dll I have:

IDType id = TypeIdGenerator<A>::GetNewID<B>();

It will be a different value to the same call made in the .exe with the same two classes:

IDType id = TypeIdGenerator<A>::GetNewID<B>();

Of course, this only happens if the types for different classes are generated in different orders.

I understand that the static members are static across each process, and the dll is another process, so how else can I easily retrieve a unique id that's consistent?

I'd prefer to use standard methods, no special compiler extensions or flags etc.

EDIT: More info on my use case.

I have a template class that holds another type so I can operate on arbitrary data using that template type. Very simplified:

class ComponentBase
{
public:
    virtual ~ComponentBase() {}

    virtual void DestroyData(unsigned char* data) const noexcept = 0;
    virtual void MoveData(unsigned char* source, unsigned char* destination) const = 0;
    virtual void ConstructData(unsigned char* data) const = 0;
};

template<class C>
class Component : public ComponentBase
{
public:
    typedef C type;

    virtual void DestroyData(unsigned char* data) const noexcept override
    {
        C* dataLocation = std::launder(reinterpret_cast<C*>(data));

        dataLocation->~C();
    }

    virtual void MoveData(unsigned char* source, unsigned char* destination) const override
    {
        new (&destination[0]) C(std::move(*reinterpret_cast<C*>(source)));
    }

    virtual void ConstructData(unsigned char* data) const override
    {
        new (&data[0]) C(*m_component);
    }
};

This is because instances of A or B need to be stored alongside each other in unsigned char* arrays. By storing a map of std::unordered_map<IDType,ComponentBase*>s I can keep access to all of the constructors, destructors, move operators etc.

I have a container that provides me information of which index in an unsigned char* is IDType for A or B etc.

So when operating on my data, I would have a container of std::vector<IDType> which from beginning to end provides me with every type stored in unsigned char* arbitrary.

I can use the IDType to lookup my unordered_map<IDType, ComponentBase*> so that I can operate on the type stored in arbitrary.

Obviously it's a little more involved than that or I'd just store the ComponentBase*s, but that's the general gist of why I need to be able to generate consistent type ids of some indexable type. Consistent only during the same execution of my program, not between different executions (though that would be a bonus).

I am using MingW64 delivered through MSYS2.



Solution 1:[1]

Instead of incrementing a counter to create the type IDs, you could instead use a hash function to generate a 'unique' hash. While there is some chance of collision, the risk is very low if the hashing function is efficient. This approach would provide consistent IDs for each type throughout an invocation of the program.

To this end, C++ actually provides a typeid operator which returns a type_info class instance. The type_info class implements a hash_code() function which is guaranteed to return a unique hash for each unique type.

The documentation notes that this hash can change between invocations of the same program, so this will not work across multiple different invocations.

The type_info class can also be used to get a unique type_index which can be used directly to index into an associate container.

Solution 2:[2]

It depends on how many of these types you are creating, but I would look for either of the following patterns.

  1. ODR and small number of objects.
template<class T> IDType TypeIdGenerator<T>::m_count = 
                  (IDType)(void*)&TypeIdGenerator<T>::m_count;

This generates a base number on the location of a pointer to itself. It doesn't cope with overflow into 2 different dlls, but if the number of types is small, this should keep the items separate.

  1. Have some communication to a single source.
IDType generator()
{
     static IDType m_count = 0;
     return m_count++;
}

So in class

template<class T>
class TypeIdGenerator
{
private:

static IDType m_count;

public:

    template<class U>
    static IDType GetNewID()
    {
        static const IDType idCounter = generate();
        return idCounter;
    }
};

With generator being implemented in a single binary (DLL) all of the code will end up calling generator for its implementation, and Id's will be process unique.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Layne Bernardo
Solution 2