11

Is it possible to implement type_id(T) in C++ which would not require manual type registration nor RTTI?

All the solutions I've seen (including boost::typeindex) are based on specialization and require manual "registration" like this:

class A {
public:
    BOOST_TYPE_INDEX_REGISTER_CLASS
    virtual ~A(){}
};

struct B: public A {
    BOOST_TYPE_INDEX_REGISTER_CLASS
};

But I wish to be able to get type id for any arbitrary type including library types which I can't redefine in my code.

Michael Z.
  • 121
  • 7
  • Not sure what you're asking for. [`typeid(T)`](http://en.cppreference.com/w/cpp/language/typeid) doesn't require RTTI; it's resolved at compile time. Only `typeid(expression)` needs RTTI. – Oktalist Aug 01 '16 at 00:15
  • My wish is to identify arbitrary types without using RTTI. Also I don't wish to modify types to be identified nor perform any manual actions to each such type. – Michael Z. Aug 01 '16 at 08:43
  • Identify in what way? Why is `typeid` not sufficient? – Oktalist Aug 01 '16 at 13:33

3 Answers3

15

Generally the answer is "no". You can't implement fair type id without RTTI or specialization.

But there is one very powerful trick. It's non-obvious so it's uncommon in C++ world.

Every modern C++ compiler supports so-called function pretty-print macro, which allows you to get unique identifier of a function with all the type parameters unwrapped.

So you may use something like the code below:

#pragma once

#undef UNIQUE_FUNCTION_ID

#if defined(_MSC_VER)
  #define UNIQUE_FUNCTION_ID __FUNCSIG__
#else     
  #if defined( __GNUG__ )
    #define UNIQUE_FUNCTION_ID __PRETTY_FUNCTION__
  #endif
#endif

template<typename T>
class TypeId
{
public:
    static int typeId()
    {
        static int s_id = HASH( UNIQUE_FUNCTION_ID );
        return s_id;
    }
};

Where HASH may be any good hash function you like.

Drawbacks:

  1. Your binary would be polluted by long char constants for each type you are using (but in real applications overhead is not so problematic, we have used this approach very intensively without significant impact on distro size. UPD: this may be avoided with constexpr)
  2. Type ids produced would not be portable across compilers, compiler versions nor even different builds (oftenly it's not a problem)
  3. Not all the compilers support macro with semantic required (MSVC, G++ and Clang work like a charm)
  4. Treats T and const T& as different types (but it may be fixed with additional processing before hashing of UNIQUE_FUNCTION_ID)

Benefits:

  1. Very easy to implement
  2. Does not require RTTI and supports arbitrary type
  3. Works for executables with DLLs/SharedObjects/DyLibs
  4. Remains stable between program executions
Pavel S.
  • 1,267
  • 14
  • 19
  • 1
    You might be able to avoid polluting the binary with lots of `char[]` constants by making `HASH` constexpr. – Oktalist Aug 01 '16 at 13:33
  • @Oktalist nice hint! – Pavel S. Aug 01 '16 at 14:15
  • 1
    (And make `s_id` constexpr so that `HASH` is used in a constant expression context.) – Oktalist Aug 02 '16 at 13:56
  • 1
    PLEASE do not do this manually. Use Boost.TypeIndex (which is not boost::typeindex) which uses this exact same trick, and will likely under a C++ 17 compiler use a far better system again automagically. See http://www.boost.org/doc/libs/1_61_0/doc/html/boost_typeindex/getting_started.html for its very simple API. – Niall Douglas Aug 03 '16 at 07:36
4

I usually use pointer to function. Since every instantiation of a template function has a different address, I get a free hashing mechanism implemented by the compiler.

Here's how I do it:

using type_id_t = void(*)();

template<typename>
void type_id() {}

That's all! Now you can use it in maps like this:

std::map<type_id_t, std::string> myMap;

myMap[type_id<int>] = "a int";
myMap[type_id<SomeType>] = "some type";
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • I tried with GCC and clang on Linux, it works just like it's supposed to! I didn't test it with msvc and DLLs, but it sure work with msvc and static libs. – Guillaume Racicot Jul 31 '16 at 21:58
  • Yep, static libs should not be a problem, but I'm pretty sure that there are some conditions when it may fail in case of DLLs. Also I think that such id will change between program exectutions. Anyway, it's a very nice trick. – Pavel S. Jul 31 '16 at 21:59
2

CRTP idiom and a C-ish types system can help in this case:

#include<cstddef>
#include<cassert>

struct B {
    static std::size_t cnt() noexcept {
        static std::size_t val = 0;
        return val++;
    }
};

template<typename T>
struct I: private B {
    static std::size_t type() noexcept {
        static std::size_t t = B::cnt();
        return t;
    }
};

struct T: I<T> { };
struct S: I<S> { };

int main () {
    assert(T::type() != S::type());

    T t1, t2;
    S s;

    assert(t1.type() == t2.type());
    assert(t1.type() != s.type());
}

You can use a macro as it follows too:

#define TypedStruct(C) struct C: I<C>

// ...

TypedStruct(T) { };
TypedStruct(S) { };

As mentioned in the comments, if you don't want it to interfere with your classes, you can use a similar approach as it follows:

#include<cstddef>
#include<cassert>

struct B {
    static std::size_t cnt() noexcept {
        static std::size_t val = 0;
        return val++;
    }
};

template<typename T>
struct Type: private B {
    static const std::size_t type;
};

template<typename T>
const std::size_t Type<T>::type = B::cnt();

struct T { };
struct S { };

int main () {
    assert(Type<T>::type != Type<S>::type);
}

As you can see, S and T are not affected by the Type class.
The latter can be used at any time to give them an unique identifier.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • This approach is very fragile and _requires_ some manual work for each type supposed to have `type_id`. The question was how to get `type_id` without altering types. – Pavel S. Jul 31 '16 at 22:03
  • @PavelS. Ok, let me add more details to explain how to use it without changing your types. – skypjack Jul 31 '16 at 22:06
  • @PavelS. Done, now you can get an unique identifier without altering your types. – skypjack Jul 31 '16 at 22:11
  • @PavelS. It won't work under Linux because of statics? What do you mean? It had been written and tested under Linux and it works indeed. Can you explain better what's your doubt? – skypjack Aug 01 '16 at 05:09
  • I mean those funny things: http://stackoverflow.com/questions/2505385/classes-and-static-variables-in-shared-libraries. It's avoidable, but it's very easy to make a very unclear mistake if we use mutable statics across dynamically linked libraries. Also I have some concerns regarding thread safety. Anyway, your solution looks interesting. – Pavel S. Aug 01 '16 at 10:15