11

I'd like a constexpr function that will return me a unique id for every C++ type, something like this:

using typeid_t = uintptr_t;

template <typename T>
constexpr typeid_t type_id() noexcept
{
  return typeid_t(type_id<T>);
}

int main()
{
  ::std::cout << ::std::integral_constant<typeid_t, type_id<float>()>{} << ::std::endl;

  return 0;
}

But I get an error:

t.cpp: In function 'int main()':
t.cpp:23:69: error: conversion from pointer type 'typeid_t (*)() noexcept {aka long unsigned int (*)() noexcept}' to arithmetic type 'typeid_t {aka long unsigned int}' in a constant-expression
   ::std::cout << ::std::integral_constant<typeid_t, type_id<float>()>{} << ::std::endl;
                                                                     ^
t.cpp:23:69: note: in template argument for type 'long unsigned int' 

Is there a workaround or another way?

user1095108
  • 14,119
  • 9
  • 58
  • 116

3 Answers3

8

You could use some tricks as shown in this answer.

There's even a library called ctti that utilizes the same trick, it should work out of the box

static_assert(ctti::type_id<int>() != ctti::type_id<float>(), "compile-time type-id comparison");

constexpr auto hash = ctti::type_id<int>().hash();
Community
  • 1
  • 1
Jamboree
  • 5,139
  • 2
  • 16
  • 36
4

Another way, this time involving a constexpr function, would be to use a well known hash function like in the following example (where I used the FNV v1a):

#include <cstdint>
#include <iostream>

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

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

constexpr uint32_t hash_str(const char *input) {
    return helper(offset, input);
}

struct MyClassA { static constexpr uint32_t type = hash_str("MyClassA"); };
struct MyClassB { static constexpr uint32_t type = hash_str("MyClassB"); };

int main() {
    std::cout << "MyClassA: " << MyClassA::type << std::endl;
    std::cout << "MyClassB: " << MyClassB::type << std::endl;
}

The drawbacks:

  • conflicts can happen
  • error-prone (at least, from my point of view)
  • pretty invasive a sollution

The main advantage is that you can use this solution if you need the types to be the same over different executions (as an example, if you have to store them somewhere and use them again after a while).

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • That's why I consider it error-prone, it's pretty easy to forget adding your string time to time. Anyway, that solution offers the chance to have persistent types, so it depends of what's the goal you want to achieve. – skypjack Mar 20 '16 at 21:10
1

This's not a constexpr function, but if there is not the constraint for the types to be persistent over multiple executions, you can use CRTP idiom as an alternative approach to achieve the same result.
It follows a minimal, working example:

#include <cstddef>
#include <iostream>

struct BaseClass {
protected:
    static std::size_t next() noexcept {
        static std::size_t counter = 0;
        return counter++;
    }
};

template<class D>
struct Base: public BaseClass {
    static std::size_t type() noexcept {
        static std::size_t type_ = BaseClass::next();
        return type_;
    }
};

struct A: public Base<A> { };
struct B: public Base<B> { };

int main() {
    std::cout << A::type() << std::endl;
    std::cout << B::type() << std::endl;
}

This way you have a base class from which to derive for all those types for which you would like to have an unique identifier.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • I like this. A class template like that could support all types, though not at compile time. – user1095108 Mar 20 '16 at 21:06
  • Another drawback is that a class can easily have different types during different executions, so this is a good solution only if you need consistency over a single execution of your software. – skypjack Mar 20 '16 at 21:12