Реализация type_id (T) в С++ без регистрации типа или RTTI

Можно ли реализовать type_id (T) в С++, который не требовал бы регистрация типа вручную или RTTI?

Все решения, которые я видел (в том числе boost:: typeindex), основаны на специализации и требуют ручной "регистрации" следующим образом:

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

struct B: public A {
    BOOST_TYPE_INDEX_REGISTER_CLASS
};

Но я хочу иметь возможность получить идентификатор типа для любого произвольного типа, включая типы библиотек, которые я не могу переопределить в своем коде.

Ответ 1

Как правило, ответ "нет". Вы не можете реализовать идентификатор справедливого типа без RTTI или специализации.

Но есть один очень сильный трюк. Это неочевидно, поэтому он редко встречается в мире С++.

Каждый современный С++-компилятор поддерживает так называемый макрос функции pretty-print, который позволяет вам получить уникальный идентификатор функции со всем типом параметры развернуты.

Итак, вы можете использовать что-то вроде кода ниже:

#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;
    }
};

Где HASH может быть любая хорошая хэш-функция, которая вам нравится.

Недостатки:

  • Ваш двоичный файл будет загрязнен длинными константами char для каждого типа, который вы используете (но в реальных приложениях накладные расходы не так проблематичны, мы очень интенсивно использовали этот подход без значительного влияния на размер дистрибутива. UPD: это может быть избегать с помощью constexpr)
  • Получаемые идентификаторы типов не будут переносимыми для компиляторов, версий компилятора и даже разных построений (часто это не проблема).
  • Не все компиляторы поддерживают макрос с семантикой (MSVC, g++ и Clang работают как прелесть)
  • Лечит T и const T& как разные типы (но он может быть исправлен с дополнительной обработкой перед хэшированием UNIQUE_FUNCTION_ID)

Преимущества:

  • Очень легко реализовать
  • Не требует RTTI и поддерживает произвольный тип
  • Работает с исполняемыми файлами с DLL/SharedObjects/DyLibs
  • Остается стабильным между выполнением программы

Ответ 2

Я обычно использую указатель на функцию. Поскольку каждый экземпляр функции шаблона имеет другой адрес, я получаю свободный хэширующий механизм, реализованный компилятором.

Вот как я это делаю:

using type_id_t = void(*)();

template<typename>
void type_id() {}

Это все! Теперь вы можете использовать его на следующих картах:

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

myMap[type_id<int>] = "a int";
myMap[type_id<SomeType>] = "some type";

Ответ 3

Идиома CRTP и система типов C-ish могут помочь в этом случае:

#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());
}

Вы также можете использовать макрос:

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

// ...

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

Как указано в комментариях, если вы не хотите, чтобы это мешало вашим классам, вы можете использовать аналогичный подход, как следует из:

#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);
}

Как вы можете видеть, S и T не подвержены влиянию класса Type.
Последний может использоваться в любое время, чтобы дать им уникальный идентификатор.