48

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.

David
  • 27,652
  • 18
  • 89
  • 138
  • Oh, I'd love to see an answer to this one. AFAIK, there is no solution with the current state of affairs in C++. – K-ballo Sep 26 '11 at 22:29
  • Macros aren't going to be reevaluated when the template is instantiated, so that's never going to work. – Mark Ransom Sep 26 '11 at 22:31
  • 1
    If you can manage to find a compile-time hash function, make a hash of `__FILE__` and `__LINE__`. – tenfour Sep 26 '11 at 22:34
  • 1
    Do the ids need to linearly increase from zero? Or just unique ids? – Bill Lynch Sep 26 '11 at 22:43
  • 6
    @tenfour: That would get evaluated the same for all `T`. – Mooing Duck Sep 26 '11 at 22:53
  • True; I'll leave the comment though in case anyone else makes the same mistake I did. Well, I doubt it's possible to hash __FILE__ at compile-time anyway. – tenfour Sep 26 '11 at 22:59
  • Does this have to be `int`, or can it be a different type (such as `intptr_t`)? – bdonlan Sep 26 '11 at 23:46
  • 2
    Also, why do you need this compile-time counter? – bdonlan Sep 26 '11 at 23:54
  • This is not possible. – n. m. could be an AI Sep 17 '16 at 23:15
  • Is c++11 acceptable? Or must it be c++03, or c++98? – Aaron McDaid Sep 18 '16 at 07:51
  • @AaronMcDaid c++11, 14, 17, all good, so long as it works. – David Sep 19 '16 at 00:39
  • 1
    What are you looking to do? Are you just looking for "uniqueness" of class A based on different typename? Or unique including subsequent compilations? For example A would have a diff id each compilation? And curious why is it so important for it to be in a case statement? – Mobile Ben Sep 19 '16 at 01:22
  • 1
    You could do [this](https://web.archive.org/web/20160811032626/http://b.atch.se/posts/constexpr-counter/) – DarthRubik Sep 22 '16 at 12:15
  • @MobileBen uniqueness based on `T`, doesn't have to change each compilation. At the time this was written people weren't understanding what a 'compile time constant' was, so it was easiest to use the switch example (cases in a switch need to be compile time constant). The result needs to be a compile time constant. – David Sep 22 '16 at 18:29
  • Instead of automating the traits class, just require that it be defined for every T. – Cheers and hth. - Alf Sep 23 '16 at 01:41
  • @DarthRubik I think that's right... I think that's the only compile-time answer so far which doesn't require defining IDs per type or listing all the types which can be used... can you make it an answer with some demo code? – David Sep 23 '16 at 21:55
  • @David I posted a solution using this method....tested it: works with switch statements – DarthRubik Sep 23 '16 at 22:52
  • Check this post: https://stackoverflow.com/questions/46872269/compile-time-hash-with-constexpr – Narf the Mouse Dec 30 '17 at 04:02

17 Answers17

11

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.

MSN
  • 53,214
  • 7
  • 75
  • 105
  • I would make that a `void *` - you're only interested in equality comparison, and so there's no need to expose the implementation detail that you're pointing to `char`s – bdonlan Sep 26 '11 at 23:48
  • 8
    Is not sufficient, each compilation unit will get its own copy of the values. To work they should be defined in a CPP, per the one definition rule, but it can't be done since they are templates. – K-ballo Sep 26 '11 at 23:57
  • @K-ballo, do you have a standard reference for that? – bdonlan Sep 26 '11 at 23:59
  • 2
    @bdonlan: For what? The one definition rule? Or the fact that static objects defined within a header file will be defined in each translation unit that includes them? Remember that `#include` is no different than copy & paste the file contents... – K-ballo Sep 27 '11 at 00:01
  • The latter. AIUI templates get special handling here. – bdonlan Sep 27 '11 at 00:28
  • I've just tested this in a multiple compilation unit test - and it worked fine. My compiler was g++ 4.0.1 on OS X, with default compilation flags. (Yes its an old compiler, and yes it doesn't mean its correct by the standard - but at least its one measurement.) – Michael Anderson Sep 27 '11 at 03:22
  • I found the relevant text in the C++11 draft standard. So yay! I'm totally right! – MSN Sep 27 '11 at 04:00
  • 1
    @MSN: Nice... but not usable at compile-time. AFAIK the compile-time generation of unique IDs is impossible. Note that using a pointer to a private static function (somewhat like the Safe Bool idiom) would work equally well, and still not answer the question. – Matthieu M. Sep 27 '11 at 06:31
  • @MatthieuM., Ah, you can't use it in a switch statement. But, it's certainly usable as a template parameter, assuming you use `ID_storage` instead of `ID`. And it even works with partial template specialization! – MSN Sep 27 '11 at 07:55
  • @MSN: I am not sure where you're going. I don't see why you would use `ID_storage` as a template parameter since its value would not identify a particular template instance anyway. And I don't get your comment on partial specialization, do you mean to specialize on `A::ID_storage` ? It would be a non-deducible context. – Matthieu M. Sep 27 '11 at 08:20
  • @MSN: why not directly use `A::ID` if all you want is an address :x ? – Matthieu M. Sep 27 '11 at 18:14
  • @MatthieuM., That's not a compile time constant expression. Like I said, not really useful. – MSN Sep 27 '11 at 18:23
  • No address is fully evaluated in compile time right? This is not compile time constant and cannot be used in a switch. – David Sep 28 '11 at 18:40
  • 1
    Just to clarify: does everyone agree that, given multiple definitions of a given function/method across many translations units, the linker must select exactly one to be used at runtime? (Perhaps there are other limitations, but this is interesting in isolation) – Aaron McDaid Sep 18 '16 at 07:58
  • You can not take the memory address of something at compile time, thus can not be used with constexpr ids. Plus it creates static objects. Also, why couldn't the value of ID be its own memory address? – QuantumKarl Nov 04 '17 at 14:07
10

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!
}
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • 1
    this is not compile time – David Sep 23 '16 at 21:51
  • The address of a function is known at compile time. So yeah, it work at runtime with `std::map` but you could use it in constexpr functions too. – Guillaume Racicot Sep 23 '16 at 21:52
  • @David The `constexpr` means that it definitely at compile time – DarthRubik Sep 24 '16 at 02:05
  • I tried this one, but was unable to get past "cannot use const void * in a switch statement". – Narf the Mouse Dec 30 '17 at 02:52
  • @NarftheMouse indeed, you won't be able to switch on pointers. My solution is not meant to be used in switch. You might be able to do it by creating a compile time string of the type name and hash the value in a `std::size_t`. Then switch will work. Using my solution, multiple if statement or a compile time table might work too. – Guillaume Racicot Dec 30 '17 at 03:02
  • 1
    It may not work on Windows. Address of function can be the same after some Visual Studio optimizations. See: https://github.com/gelldur/EventBus/issues/19 – Gelldur Dec 25 '19 at 00:48
  • @DawidDrozd clang on windows also can let duplicates of inline functions. This is why I tried this instead: https://codereview.stackexchange.com/questions/209950/unique-compile-time-constexpr-type-id-without-rtti – Guillaume Racicot Dec 25 '19 at 00:56
  • @GuillaumeRacicot thanks for the link. It would be nice if you could update your answer :) – Gelldur Dec 25 '19 at 01:05
5

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.

Community
  • 1
  • 1
J. Calleja
  • 4,855
  • 2
  • 33
  • 54
  • 3
    To reduce collision probability to comfortable level you can use [constexpr MD5 function](https://github.com/elbeno/constexpr/blob/master/src/include/cx_md5.h) with [__int128 type](https://gcc.gnu.org/onlinedocs/gcc/_005f_005fint128.html) for ID. – Roman Khimov Sep 17 '16 at 09:52
  • I can submit famous DJB2 hash function that works at compile time too, here it is: https://wandbox.org/permlink/zcghdF5jXQiGJ6CM, I mean as an more sober alternative to COMPILE_TIME_CRC32_STR –  Oct 18 '18 at 08:22
4

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;

}
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
3

Using this constant expression counter:

template <class T>
class A
{
public:
    static constexpr int ID() { return next(); }
};
class DUMMY { };
int main() {
    std::cout << A<char>::ID() << std::endl;
    std::cout << A<int>::ID() << std::endl;
    std::cout << A<BETA>::ID() << std::endl;
    std::cout << A<BETA>::ID() << std::endl;
    return 0;
}

output: (GCC, C++14)

1
2
3
3

The downside is you will need to guess an upper bound on the number of derived classes for the constant expression counter to work.

kdavison
  • 31
  • 2
3

Use the memory address of a static function.

template<typename T>
class A  {
public:
    static void ID() {}
}; 

(&(A<int>::ID)) will be different from (&(A<char>::ID)) and so on.

Pubby
  • 51,882
  • 13
  • 139
  • 180
  • Compiler complains: "‘ID’ was not declared in this scope" + "‘&’ cannot appear in a constant-expression" – foxcub Sep 26 '11 at 23:01
  • 3
    In order to take the address of a static constant integral (note your missing the const modifier), such integral has to be defined in an appropiate CPP file which cannot be done with templates. Is undefined behavior otherwise; and if the static is declared within the header file you'll get different addresses at different compilation units. – K-ballo Sep 26 '11 at 23:01
  • Does the C++ standard really guarantee this for ints? This can certainly fail on 64-bit platforms at least – bdonlan Sep 26 '11 at 23:35
  • Also, does the C++ standard even guarantee that template function pointers will compare equal like this? – bdonlan Sep 26 '11 at 23:46
  • 1
    @bdonlan: Actually that's not guaranteed since it will get different addresses in different compilation units. Besides, even within the same compilation unit, compilers like MSVC refuse that as they optimize all functions to point to the same one. – K-ballo Sep 26 '11 at 23:56
2

Here is a possible solution mostly based on templates:

#include<cstddef>
#include<functional>
#include<iostream>

template<typename T>
struct wrapper {
    using type = T;
    constexpr wrapper(std::size_t N): N{N} {}
    const std::size_t N;
};

template<typename... T>
struct identifier: wrapper<T>... {
    template<std::size_t... I>
    constexpr identifier(std::index_sequence<I...>): wrapper<T>{I}... {}

    template<typename U>
    constexpr std::size_t get() const { return wrapper<U>::N; }
};

template<typename... T>
constexpr identifier<T...> ID = identifier<T...>{std::make_index_sequence<sizeof...(T)>{}};

// ---

struct A {};
struct B {};

constexpr auto id = ID<A, B>;

int main() {
    switch(id.get<B>()) {
    case id.get<A>():
        std::cout << "A" << std::endl;
        break;
    case id.get<B>():
        std::cout << "B" << std::endl;
        break;
    }
}

Note that this requires C++14.

All you have to do to associate sequential ids to a list of types is to provide that list to a template variable as in the example above:

constexpr auto id = ID<A, B>;

From that point on, you can get the given id for the given type by means of the get method:

id.get<A>()

And that's all. You can use it in a switch statement as requested and as shown in the example code.

Note that, as long as types are appended to the list of classes to which associate a numeric id, identifiers are the same after each compilation and during each execution.
If you want to remove a type from the list, you can still use fake types as placeholders, as an example:

template<typename> struct noLonger { };
constexpr auto id = ID<noLonger<A>, B>;

This will ensure that A has no longer an associated id and the one given to B won't change.
If you won't to definitely delete A, you can use something like:

constexpr auto id = ID<noLonger<void>, B>;

Or whatever.

skypjack
  • 49,335
  • 19
  • 95
  • 187
2

Ok.....so this is a hack that I found from this website. It should work. The only thing you need to do is add another template parameter to your struct that takes a counter "meta-object". Note that A with int, bool and char all have unique IDs, but it is not guaranteed that int's will be 1 and bool will be 2, etc., because the order in which templates are initiated is not necessarily known.

Another note:

This will not work with Microsoft Visual C++

#include <iostream>
#include "meta_counter.hpp"

template<typename T, typename counter>
struct A
{
    static const size_t ID = counter::next();
};

int main () {
    typedef atch::meta_counter<void> counter;
    typedef A<int,counter> AInt;
    typedef A<char,counter> AChar;
    typedef A<bool,counter> ABool;
    switch (ABool::ID)
    {
        case AInt::ID:
            std::cout << "Int\n";
            break;
        case ABool::ID:
            std::cout << "Bool\n";
            break;
        case AChar::ID:
            std::cout << "Char\n";
            break;
    }

    std::cout << AInt::ID << std::endl;
    std::cout << AChar::ID << std::endl;
    std::cout << ABool::ID << std::endl;
    std::cout << AInt::ID << std::endl;
    while (1) {}
}

Here is meta_counter.hpp:

// author: Filip Roséen <filip.roseen@gmail.com>
// source: http://b.atch.se/posts/constexpr-meta-container

#ifndef ATCH_META_COUNTER_HPP
#define ATCH_META_COUNTER_HPP

#include <cstddef>

namespace atch { namespace {

  template<class Tag>
  struct meta_counter {
    using size_type = std::size_t;

    template<size_type N>
    struct ident {
      friend constexpr size_type adl_lookup (ident<N>);
      static constexpr size_type value = N;
    };

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

    template<class Ident>
    struct writer {
      friend constexpr size_type adl_lookup (Ident) {
        return Ident::value;
      }

      static constexpr size_type value = Ident::value;
    };

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

    template<size_type N, int = adl_lookup (ident<N> {})>
    static constexpr size_type value_reader (int, ident<N>) {
      return N;
    }

    template<size_type N>
    static constexpr size_type value_reader (float, ident<N>, size_type R = value_reader (0, ident<N-1> ())) {
      return R;
    }

    static constexpr size_type value_reader (float, ident<0>) {
      return 0;
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

    template<size_type Max = 64>
    static constexpr size_type value (size_type R = value_reader (0, ident<Max> {})) {
      return R;
    }

    template<size_type N = 1, class H = meta_counter>
    static constexpr size_type next (size_type R = writer<ident<N + H::value ()>>::value) {
      return R;
    }
  };
}}

#endif /* include guard */
DarthRubik
  • 3,927
  • 1
  • 18
  • 54
  • question: Why does A have a template argument `counter`? Could it not just use `meta_counter` directly? And is the only purpose to the `meta_counter` `Tag` to support having multiple counts? – David Sep 27 '16 at 19:00
  • @David The only purpose of the `Tag` argument is to support multiple counts (like I said I did not write a lot of this code). No if you just have `meta_counter` all the classes have the same ID (I tried that initially though). That said, you could probably do some template trickery so that is transparent if the need was there to do that. – DarthRubik Sep 27 '16 at 20:14
  • innterrestttinnnng. I'll have to read about why having `meta_counter` not as a template argument wouldn't work. That doesn't make a lot of sense to me. Something about template instantiation? – David Sep 27 '16 at 20:36
  • @David I don't know the answer to that question. All I did was experiment a little. – DarthRubik Sep 27 '16 at 20:39
  • Copied and pasted your code, it doesn't seem to work. All the IDs are the same. Using clang / xcode 8 / c++14 – David Sep 27 '16 at 20:45
  • @David using c++ online shell it [works](http://www.cpp.sh/2fpdi)......I don't have access to clang so I don't know what to say – DarthRubik Sep 28 '16 at 11:59
2

using template and if constexpr, need c++17

#include <iostream>

template <typename Type, typename... Types>
struct TypeRegister{
    template<typename Queried_type>
    static constexpr int id(){
        if constexpr (std::is_same_v<Type, Queried_type>) return 0;
        else{
            static_assert((sizeof...(Types) > 0), "You shan't query a type you didn't register first");
            return 1 + TypeRegister<Types...>::template id<Queried_type>();
        }
    }
};

int main(){
    using reg_map = TypeRegister<int, float, char, const int&>;
    std::cout << reg_map::id<const int&>() << std::endl;// 3
    // std::cout << reg_map::id<const int>() << std::endl;// error
}
treert
  • 51
  • 2
2

I encountered this exact problem recently. My solution:

counter.hpp

class counter
{
    static int i;
    static nexti()
    {
        return i++;
    }
};

Counter.cpp:

int counter::i = 0;

templateclass.hpp

#include "counter.hpp"

    template <class T>
    tclass
    {
        static const int id;
    };

    template <class T>
    int tclass<T>::id = counter::nexti();

It appers to work properly in MSVC and GCC, with the one exception that you can't use it in a switch statement.

For various reasons I actually went further, and defined a preprocessor macro that creates a new class from a given name parameter with a static ID (as above) that derives from a common base.

Craig
  • 37
  • 2
1

I had a similar problem a few months ago. I was looking for a technique to define identifiers that are the same over each execution.
If this is a requirement, here is another question that explores more or less the same issue (of course, it comes along with its nice answer).
Anyway I didn't use the proposed solution. It follows a description of what I did that time.


You can define a constexpr function like the following one:

static constexpr uint32_t offset = 2166136261u;
static constexpr uint32_t prime = 16777619u;

constexpr uint32_t fnv(uint32_t partial, const char *str) {
    return str[0] == 0 ? partial : fnv((partial^str[0])*prime, str+1);
}

inline uint32_t fnv(const char *str) {
    return fnv(offset, str);
}

Then a class like this from which to inherit:

template<typename T>
struct B {
    static const uint32_t id() {
        static uint32_t val = fnv(T::identifier);
        return val;
    }
};

CRTP idiom does the rest.
As an example, you can define a derived class as it follows:

struct C: B<C> {
    static const char * identifier;
};

const char * C::identifier = "ID(C)";

As long as you provide different identifiers for different classes, you will have unique numeric values that can be used to distinguish between the types.

Identifiers are not required to be part of the derived classes. As an example, you can provide them by means of a trait:

template<typename> struct trait;
template<> struct trait { static const char * identifier; };

// so on with all the identifiers

template<typename T>
struct B {
    static const uint32_t id() {
        static uint32_t val = fnv(trait<T>::identifier);
        return val;
    }
};

Advantages:

  • Easy to implement.
  • No dependencies.
  • Numeric values are the same during each execution.
  • Classes can share the same numeric identifier if needed.

Disadvantages:

  • Error-prone: copy-and-paste can quickly become your worst enemy.

It follows a minimal, working example of what has been described above.
I adapted the code so as to be able to use the ID member method in a switch statement:

#include<type_traits>
#include<cstdint>
#include<cstddef>

static constexpr uint32_t offset = 2166136261u;
static constexpr uint32_t prime = 16777619u;

template<std::size_t I, std::size_t N>
constexpr
std::enable_if_t<(I == N), uint32_t>
fnv(uint32_t partial, const char (&)[N]) {
    return partial;
}

template<std::size_t I, std::size_t N>
constexpr
std::enable_if_t<(I < N), uint32_t>
fnv(uint32_t partial, const char (&str)[N]) {
    return fnv<I+1>((partial^str[I])*prime, str);
}

template<std::size_t N>
constexpr inline uint32_t fnv(const char (&str)[N]) {
    return fnv<0>(offset, str);
}

template<typename T>
struct A {
    static constexpr uint32_t ID() {
        return fnv(T::identifier);
    }
};

struct C: A<C> {
    static constexpr char identifier[] = "foo";
};

struct D: A<D> {
    static constexpr char identifier[] = "bar";
};

int main() {
    constexpr auto val = C::ID();

    switch(val) {
    case C::ID():
        break;
    case D::ID():
        break;
    default:
        break;
    }
}

Please, note that if you want to use ID in a non-constant expression, you must define somewhere the identifiers as it follows:

constexpr char C::identifier[];
constexpr char D::identifier[];

Once you did it, you can do something like this:

int main() {
    constexpr auto val = C::ID();
    // Now, it is well-formed
    auto ident = C::ID();

    // ...
}
Community
  • 1
  • 1
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • If you're requiring every `T` to have a unique `identifier` static member string, that defeats the point - they each already have a unique compile time identifier – David Sep 22 '16 at 18:19
  • @David If your requirement is to have a numeric identifier (differences between compare integers vs compare strings, likely I have not to provide more details about this), but still you want to use a handy and easy to use identifier that is thrown away at compile time... Et voilà!! – skypjack Sep 22 '16 at 18:57
1

Here is a C++ code that uses __DATE__ and __TIME__ macro to get unique identifiers for types <T>

Format:

// __DATE__ "??? ?? ????"
// __TIME__ "??:??:??"

This is a poor quality hash function:

#define HASH_A 8416451
#define HASH_B 11368711
#define HASH_SEED 9796691    \
+ __DATE__[0x0] * 389        \
+ __DATE__[0x1] * 82421      \
+ __DATE__[0x2] * 1003141    \
+ __DATE__[0x4] * 1463339    \
+ __DATE__[0x5] * 2883371    \
+ __DATE__[0x7] * 4708387    \
+ __DATE__[0x8] * 4709213    \
+ __DATE__[0x9] * 6500209    \
+ __DATE__[0xA] * 6500231    \
+ __TIME__[0x0] * 7071997    \
+ __TIME__[0x1] * 10221293   \
+ __TIME__[0x3] * 10716197   \
+ __TIME__[0x4] * 10913537   \
+ __TIME__[0x6] * 14346811   \
+ __TIME__[0x7] * 15485863

unsigned HASH_STATE = HASH_SEED;
unsigned HASH() {
    return HASH_STATE = HASH_STATE * HASH_A % HASH_B;
}

Using the hash function:

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

template <>
const unsigned int A<float>::ID = HASH();

template <>
const unsigned int A<double>::ID = HASH();

template <>
const unsigned int A<int>::ID = HASH();

template <>
const unsigned int A<short>::ID = HASH();

#include <iostream>

int main() {
    std::cout << A<float>::ID << std::endl;
    std::cout << A<double>::ID << std::endl;
    std::cout << A<int>::ID << std::endl;
    std::cout << A<short>::ID << std::endl;
}
Szabolcs Dombi
  • 5,493
  • 3
  • 39
  • 71
1

This can't be done. An address to a static object is the closest you can get to a unique id, however in order to take addresses of such objects (even static const integrals) they must be defined somewhere. Per the one definition rule, they should be defined within a CPP file, which cannot be done since they are templates. If you define the statics within a header file, then each compilation unit will get its own version of it implemented of course at different addresses.

K-ballo
  • 80,396
  • 20
  • 159
  • 169
0

Another alternative is to consider the following class Data with the unique, static member field type:

template <class T>
class Data
{
public:
    static const std::type_index type;
};
// do [static data member initialization](http://stackoverflow.com/q/11300652/3041008)
// by [generating unique type id](http://stackoverflow.com/q/26794944/3041008)
template <class T>
std::type_index const Data<T>::type = std::type_index(typeid(T));

produces the output (MinGWx64-gcc4.8.4 -std=c++11 -O2)

printf("%s %s\n", Data<int>::type.name(), Data<float>::type.name())
//prints "i f"

It's not exactly an integer id or pretty-printable string, nor a constexpr, but can be used as an index in (un)ordered associative containers.
It also appears to work if the Data.h header is included in multiple files (same hashCode() values).

mucaho
  • 2,119
  • 20
  • 35
  • Right, to quote yourself: _"All the answers based on [static initialization] 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"_ – mucaho Apr 01 '17 at 01:09
0

Here is a pragmatic solution, if you are ok with writing a single additional line DECLARE_ID(type) for each type you want to use:

 #include <iostream>

 template<class> struct my_id_helper;
 #define DECLARE_ID(C) template<> struct my_id_helper<C> { enum {value = __COUNTER__ }; }

 // actually declare ids:
 DECLARE_ID(int);
 DECLARE_ID(double);
 // this would result in a compile error: redefinition of struct my_id_helper<int>’
 // DECLARE_ID(int);

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

 int main()
 {
     switch(A<int>::ID)
     {
     case A<int>::ID:    std::cout << "it's an int!\n"; break;
     case A<double>::ID: std::cout << "it's a double!\n"; break;
     // case A<float>::ID: // error: incomplete type ‘my_id_helper<float>’
     default: std::cout << "it's something else\n"; break;
     }
 }
chtz
  • 17,329
  • 4
  • 26
  • 56
0
template<typename T>
static void get_type_id() { void* x; new (x) T(); }
using type_id_t = void(*)();

works fine with optimizations

iperov
  • 455
  • 7
  • 8
0

If non-monotonous values and an intptr_t are acceptable:

template<typename T>
struct TypeID
{
private:
    static char id_ref;
public:
    static const intptr_t ID;
};

template<typename T>
  char TypeID<T>::id_ref;
template<typename T>
  const intptr_t TypeID<T>::ID = (intptr_t)&TypeID<T>::id_ref;

If you must have ints, or must have monotonically incrementing values, I think using static constructors is the only way to go:

// put this in a namespace
extern int counter;

template<typename T>
class Counter {
private:
  Counter() {
    ID_val = counter++;
  }
  static Counter init;
  static int ID_val;
public:
  static const int &ID;
};

template<typename T>
  Counter<T> Counter<T>::init;
template<typename T>
  int Counter<T>::ID_val;
template<typename T>
  const int &Counter<T>::ID = Counter<T>::ID_val;

// in a non-header file somewhere
int counter;

Note that neither of these techniques is safe if you are sharing them between shared libraries and your application!

bdonlan
  • 224,562
  • 31
  • 268
  • 324
  • 2
    Could you explain what you mean by shared libraries (dll, lib or else) and why it's not safe to share between them. – ALOToverflow Mar 24 '13 at 18:40