Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

implementing type_id(T) in C++ without type registration or RTTI

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.

like image 354
Michael Z. Avatar asked Jul 31 '16 20:07

Michael Z.


People also ask

Does Typeid require RTTI?

The typeid operator requires RunTime Type Identification (RTTI) to be generated, which must be explicitly specified at compile time through a compiler option.

Why is RunTime type identification RTTI a necessary feature of C++?

Run-time type identification (RTTI) lets you find the exact type of an object when you have only a pointer or reference to the base type. This can be thought of as a “secondary” feature in C++, a pragmatism to help out when you get into messy situations.

Why do we need RTTI?

RTTI, Run-Time Type Information, introduces a [mild] form of reflection for C++. It allows to know for example the type of a super class, hence allowing to handle an heterogeneous collection of objects which are all derived from the same base type. in ways that are specific to the individual super-classes.

When to use RTTI?

RTTI is available only for classes that are polymorphic, which means they have at least one virtual method. In practice, this is not a limitation because base classes must have a virtual destructor to allow objects of derived classes to perform proper cleanup if they are deleted from a base pointer.


3 Answers

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
like image 100
Pavel S. Avatar answered Oct 02 '22 22:10

Pavel S.


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";
like image 34
Guillaume Racicot Avatar answered Oct 02 '22 21:10

Guillaume Racicot


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.

like image 32
skypjack Avatar answered Oct 02 '22 21:10

skypjack