Как я могу генерировать плотные уникальные идентификаторы типов во время компиляции?

Я пытаюсь создать систему классов, которые являются маленькими объектами, а базовый класс имеет член, который является уникальным идентификатором, который идентифицирует класс:

class Shape
{
public:
    unsigned char id;
};

template <typename T>
class Triangle : public Shape
{
    T triangle_data;
};

template <typename T>
class Square : public Shape
{
    T square_data;
};

template <typename T>
class ShapeBox : public Shape
{
    T shapebox_data;
    Shape * child_shape;
};

С идентификатором класса я просматриваю вектор Shape * и переключаю идентификатор, видимый в базовом классе, а затем статический приведение для различного поведения (в треугольник, квадрат или ShapeBox и дочерние фигуры, удерживаемые в нем соответственно для пример иерархии классов)

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

В настоящее время я могу сделать идентификаторы типов с помощью статики, которым назначены значения из статического монотонно увеличивающего счетчика:

class TypeID
{
    static size_t counter;

public:
    template<typename T>
    static size_t value()
    {
        static size_t id = counter++;
        return id;
    }
};
size_t TypeID::counter = 1;

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

Я могу использовать шаблон во время компиляции, чтобы вручную назначать каждый идентификатор типа, или я могу использовать указатели объектов/функций из аналогичного типа. Пластинчатая плита гарантированно плотна (потому что мы ее назначаем вручную) и известна во время компиляции, но она не может быть использована для типов шаблонов. Всякий раз, когда вы добавляете тип шаблона в фигуру, вам необходимо вручную добавить новый тип. Монотонический статический счетчик является поддерживаемым и плотным, но неизвестным во время компиляции, и поэтому оптимизация времени компиляции невозможна, и проблема безопасности потоков может быть проблемой. Идентификатор указателя функции известен во время компиляции и поддерживается, но не является плотным и не будет вписываться в небольшой тип id, такой как char.

Есть ли способ генерировать идентификаторы типов, видимые компилятору во время компиляции, плотные и автоматически назначаемые, возможно, используя счетчик метапрограмм шаблонов или некоторую магию препроцессора в С++ 11 или С++ 14? Или это невозможно, пока С++ не компилирует временное отражение?

Ответ 1

Есть ли способ генерировать идентификаторы типов, которые видны компилятору во время компиляции, плотно и автоматически назначается, возможно, используя шаблон метапрограммирования счетчик

Я разработал код, который делает это с небольшими ограничениями. Две специализированные специализации ID_by_T и T_by_ID определяют связь type <=> ID во время компиляции. Идентификатор типа - это константа перечисления. Если ссылка type <=> ID не определена ID_by_T<type>::ID is -1 и T_by_ID<undefinedID>::type является null_t предопределенным типом. Макрос DEF_TYPE_ID(type_name) генерирует новый идентификатор при определении ссылки type <=> ID. int_triangle и char_triangle показывают, как получить typedef с правильным идентификатором типа и внутренним typedef _MyID_T показывает, как получить идентификатор типа. Код был скомпилирован с MS VS 2005 С++.

Заголовочный файл core - type_id_map:

#ifndef __TYPE_ID_MAP__
#define __TYPE_ID_MAP__

namespace ns_type_ids {
    struct null_t {};
    template<class T, int ID_v>
    struct ID_T_pair {
        enum {ID=ID_v};
        typedef T _Type;
        inline static const _TCHAR * name() { return _T("unknown"); }
    };

    template<class T> struct ID_by_T: ID_T_pair<T, -1> {};
    template<int ID_v> struct T_by_ID: ID_T_pair<null_t, ID_v> {};

    template<> struct ID_by_T<null_t>: ID_T_pair<null_t, -1> {
        inline static const _TCHAR * name() { return _T("null_t"); }
    };
    template<> struct T_by_ID<ID_by_T<null_t>::ID>: ID_by_T<null_t> {};
};

#define PREV_TYPE null_t
#define DEF_TYPE_ID(type_name) \
namespace ns_type_ids { \
    template<> struct ID_by_T<type_name>: ID_T_pair<type_name, ID_by_T<PREV_TYPE>::ID+1> { \
        inline static const _TCHAR * name() { return _T(#type_name); } \
    }; \
    template<> struct T_by_ID<ID_by_T<type_name>::ID>: ID_by_T<type_name> {}; \
};

#endif

И использование примера type_id_map в templated_cls_id.cpp:

#include <tchar.h>
#include <iostream>

#ifdef _UNICODE
#define _tcout wcout
#else
#define _tcout cout
#endif


#include "type_id_map"    

//targeted types
struct shape {};

template<class T>
struct data_t: shape {
    T _data;
};

template<class T>
struct triangle: data_t<T>, ns_type_ids::ID_by_T<data_t<T> > {
    typedef data_t<T> _MyID_T;
};

//begin type <=> id map
DEF_TYPE_ID(int)
#undef  PREV_TYPE
#define PREV_TYPE int

DEF_TYPE_ID(double)
#undef  PREV_TYPE
#define PREV_TYPE double

DEF_TYPE_ID(float)
#undef  PREV_TYPE
#define PREV_TYPE float

DEF_TYPE_ID(data_t<int>)
#undef  PREV_TYPE
#define PREV_TYPE data_t<int>

DEF_TYPE_ID(data_t<char>)
#undef  PREV_TYPE
#define PREV_TYPE data_t<char>
//end type <=> id map

//Now targeted classes could be defined
typedef triangle<int> int_triangle;
typedef triangle<char> char_triangle;

int _tmain(int argc, _TCHAR* argv[]) {
    using namespace std;
    using namespace ns_type_ids;
#define out_id(type_name) \
    _T("ID_by_T<") _T(#type_name) _T(">::ID: ") << ID_by_T<type_name>::ID
#define out_name(type_id) \
    _T("T_by_ID<") _T(#type_id) _T(">: ") << T_by_ID<type_id>::name()

    _tcout
        << out_id(null_t) << endl
        << out_id(int) << endl
        << out_id(double) << endl
        << out_id(float) << endl
        << out_id(int_triangle::_MyID_T) << endl
        << out_id(char_triangle::_MyID_T) << endl

        << out_name(-1) << endl
        << out_name(0) << endl
        << out_name(1) << endl
        << out_name(2) << endl
        << out_name(3) << endl
        << out_name(4) << endl
    ;
    return 0;
#undef out_id
#undef out_name
}

Вывод:

ID_by_T<null_t>::ID: -1
ID_by_T<int>::ID: 0
ID_by_T<double>::ID: 1
ID_by_T<float>::ID: 2
ID_by_T<int_triangle::_MyID_T>::ID: 3
ID_by_T<char_triangle::_MyID_T>::ID: 4
T_by_ID<-1>: null_t
T_by_ID<0>: int
T_by_ID<1>: double
T_by_ID<2>: float
T_by_ID<3>: data_t<int>
T_by_ID<4>: data_t<char>

Требования и ограничения:

  • type <=> ID карта глобальна и работает только во время компиляции.
  • type <=> ID ссылка должна быть определена на глобальном уровне пространства имен с помощью макросов DEF_TYPE_ID и PREV_TYPE.
  • Тип "IDed" должен быть объявлен до определения ссылки type <=> ID.
  • Чтобы получить идентификатор внутри класса, используйте ID_by_T<self_type> как базовый класс, где self_type - это собственный тип класса. Но почему (см. Ниже)?
  • Чтобы получить идентификатор self внутри шаблонизированного класса, используйте ID_by_T<base_data_type> в качестве базового класса, где base_data_type является специальным базовым типом шаблонного шаблона, для которого ссылка type <=> ID уже определена. См. Например, int_triangle и char_triangle. Также есть другие трюки, чтобы получить определенный идентификатор внутри экземпляра шаблона.

Функции

  • Идентификаторы внешние и распределяются во время компиляции автоматически последовательно, начиная с 0 в порядке компиляции type <=> ID определений ссылок. Это неизбежность из-за требования вопроса.
  • Минимальные требования к компилятору: только стандартные функции ISO/IEC 14882:2003 SE.
  • Макросы __COUNTER__, __LINE__, BOOST_PP_COUNTER или на основе sizeof не используются для выделения идентификатора: нет связанные с ними побочные эффекты.
  • Карта type <=> ID основана на внешних идентификаторах, известных во время компиляции:
    • Идентификатор может быть присвоен каждому типу даже базовому типу.
    • ID_T_pair шаблон описывает ссылку type <=> ID. Шаблоны ID_by_T/T_by_ID являются прямыми потомками шаблона ID_T_pair.
    • Из-за шаблона ID_by_T нет необходимости определять идентификатор внутри типа (что невозможно для основных типов).
    • Идентификатор по типу осуществляется с помощью константы ID_by_T<type>::ID enum.
    • Доступ к типу по идентификатору осуществляется с помощью T_by_ID<ID>::_Type внутреннего typedef.
    • Необязательно: имя типа доступно с помощью метода name() ID_T_pair.
    • Карта не занимает никакого байта памяти, если метод name() of ID_T_pair не используется.
  • Карта распределена: type <=> ID ссылка может быть определена на месте, но на глобальном уровне пространства имен.
  • Для доступа к карте в нескольких TU может использоваться специальный заголовок.
  • Определение сложного производного типа не требует дополнительной информации для карты.
  • На карте используется специальный нулевой тип null_t и ID=-1, возвращенный при отсутствии ссылки type <=> ID.

Ответ 2

Сегодня я разработал другое решение для автоматической установки идентификатора для каждого экземпляра шаблона без необходимости определять псевдоним для каждого экземпляра шаблона "IDed". Решение с именем v2 основано на предыдущем упоминании как v1. Функция v1 для назначения идентификатора базовому типу требуется для автоматического присвоения уникального идентификатора каждому экземпляру шаблона.

UPD: важное замечание о выборе единственного идентификатора-идентификатора

Проблема, рассматриваемая здесь, связана с задачей, но оба ответа. Подходы к задаче подразумевают единственный идентификатор-идентификатор из-за требований:

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

и

Мне нужен плотный уникальный идентификатор типа, доступный во время компиляции, поэтому компилятор может работать хорошо, например, конвертировать переключатель по типу Идентификаторы в таблицу перехода по постоянному времени или, по крайней мере, двоичное дерево поиска

Если используется более одного ID-распределителя (в случае нескольких библиотек и разработчиков), и TU должен быть сопряжен с IDed-типами, тогда единственный способ - использовать GUID, значения которых разрежены и не являются последовательными. Но также GUID занимает 16 байт и требует RTTI, а также отражения типа. В противном случае, пытаясь построить две библиотеки (которые абсолютно имеют разные карты type <=> id), сопряженные с идентификатором типов в один модуль, либо компоновщик генерирует ошибку (благодаря @MooingDuck для замечаний), либо разработчики будут мешать их различным назначениям идентификаторов к типам, полученным либо вручную (с использованием макроса v2 DEF_TYPE_ID), либо генератора идентификаторов (вызов макроса макроса AUTO_TYPE_ID).

Итак, есть случаи, которые всегда должны быть сведены к первому, чтобы использовать int ID типов:

  • существует единственный идентификатор выделения и единственная карта type <=> id для всех TU;
  • интерфейс TU не зависит от "системы классов, которые являются небольшими объектами" с ссылками type <=> id, следовательно, первый случай для каждого TU;
  • для получения единственной карты type <=> id должно быть выполнено согласование между разработчиками на картах type <=> id, созданных разными идентификаторами-распределителями.

Существует другой подход, но который не соответствует требованию "генерировать плотные идентификаторы". Подход позволяет частично автоматически сгенерировать структурированный идентификатор, например идентификатор, например enum {FileID, LocalID}; typedef get_id<arg1_t>::res tmpl_arg_1_ID; .... В этом случае FileID должен быть назначен вручную для каждого файла, где определена ссылка type <=> id. LocalID генерируется вызовом компилятора с макросом __LINE__. LocalID шаблона автоматически назначается способом, описанным ниже. Идентификаторы шаблонных аргументов, такие как tmpl_arg_1_ID, получают автоматически с использованием шаблона get_id. Основным преимуществом таких структурированных идентификаторов является то, что они являются статическими и постоянными для каждой библиотеки и TU из-за постоянного содержимого включенных файлов (но управление версиями становится проблемой). Чтобы применить такой структурированный идентификатор, могут использоваться несколько вложенных операторов switch, начиная с FileID, затем LocalID и т.д.

Особенности и отличия от v1

  • Каждый экземпляр шаблона автоматически принимает уникальный идентификатор и не требует объявления псевдонима или пересылки из-за того, что идентификатор выделен и определен как внутренняя константа enum.
  • Шаблон принимает собственный уникальный идентификатор, определенный как константа перечисления, внутри специального "нулевого" экземпляра шаблона, такого как T<null_t, null_t ...> с именем _BaseT, где тип null_t задается для всех аргументов типа.
  • Разрешены только идентификаторы шаблонных insnances: они являются значениями хэш-функции, вычисленной из идентификатора параметров шаблона и идентификатора шаблона, доступ к которому можно получить как _BaseT::ID. Хэш-функция такая же, как в заголовке xhash в MS VS 2005.
  • Теперь карта type <=> id использует ID=0, возвращенный в отсутствие ссылки type <=> id.
  • По умолчанию экземпляр шаблона не имеет связанной ссылки type <=> id на карте. Вот почему шаблон get_id используется для доступа к идентификатору по типу. Макрос
  • __COUNTER__ используется для уменьшения и упрощения кода: макрос PREV_TYPE больше не нужен.
  • Нет необходимости использовать трюк с шаблоном data_t из v1 из-за передовых объявлений и внутреннего идентификатора экземпляра шаблона.
  • Теперь type <=> id ссылка на автоматически сгенерированный идентификатор должна быть определена макросом AUTO_TYPE_ID(type_name).
  • Также ссылка type <=> id может быть определена с идентификатором, выделенным другим распределителем (т.е. вручную) с использованием макроса DEF_TYPE_ID(type_name, id). Но если вы используете оба макроса, разрешение проблем с идентификаторами конфликтов становится проблемой.

Ядро - type_id_map_t_cnt Заголовок

#ifndef __TYPE_ID_MAP_T_CNT__
#define __TYPE_ID_MAP_T_CNT__

//use it if there is __COUNTER__ macro and rarefied random ID is allowable
namespace ns_type_ids {
    typedef unsigned int uint;
    typedef unsigned long long ulint;
    typedef unsigned short ushort;

    //`type <=> id` link
    struct null_t { enum {ID=__COUNTER__}; };
    template<class T, int ID_v>
    struct ID_T_pair {
        enum {ID=ID_v};
        typedef T _Type;
        inline static const _TCHAR * name() { return _T("unassigned"); }
    };

    //accessors for `type <=> id` link
    template<class T> struct ID_by_T: ID_T_pair<T, null_t::ID> {};
    template<int ID_v> struct T_by_ID: ID_T_pair<null_t, ID_v> {};

    //predefined `type <=> id` link for null_t and ID=0
    template<> struct ID_by_T<null_t>: ID_T_pair<null_t, null_t::ID> {
        inline static const _TCHAR * name() { return _T("null_t"); }
    };
    template<> struct T_by_ID<ID_by_T<null_t>::ID>: ID_by_T<null_t> {};

    //function for generating IDs inside an instance of class template
    //2166136261U and 16777619U constants are from xhash STL implementation
    template<ushort v, uint a=2166136261U>
    struct hash {
        enum : uint {res=(uint)((ulint)16777619U * (ulint)a ^ (ulint)v)};
    };

    //ternary operator ?:
    template <bool, class Yes, class No>struct IIF { typedef null_t res; };
    template <class Yes, class No> struct IIF<true, Yes, No> { typedef Yes res; };
    template <class Yes, class No> struct IIF<false, Yes, No> { typedef No res; };

    //accessor to ID of type for both `type <=> ID` link and ID of a template instance
    template <class T>
    struct get_id {
        typedef typename IIF<
        //by default there is no `type <=> ID` link for a teamplate instance
        //instead ID is allocated and defined inside.
            ID_by_T<T>::ID == null_t::ID
        ,   T
        ,   ID_by_T<T>
        >::res _IDed;
        // this `::ID` interface coincedences for
        // ID_T_pair, a template instance T and null_t
        enum : uint {res=_IDed::ID};
    };
};

// DEF_TYPE_ID macro to define `type <=> id` link
#define DEF_TYPE_ID(type_name, type_id) \
namespace ns_type_ids { \
    template<> struct ID_by_T<type_name>: ID_T_pair<type_name, type_id> { \
        inline static const _TCHAR * name() { return _T(#type_name); } \
    }; \
    template<> struct T_by_ID<ID_by_T<type_name>::ID>: ID_by_T<type_name> {}; \
};

// AUTO_TYPE_ID macro to allocate new ID and define `type <=> id` link
#define AUTO_TYPE_ID(type_name) DEF_TYPE_ID(type_name, __COUNTER__)

#endif /* __TYPE_ID_MAP_T_CNT__ */

Использование примера карты type <=> id в templated_cls_id.cpp

#include <tchar.h>
#include <iostream>

#ifdef _UNICODE
#define _tcout wcout
#else
#define _tcout cout
#endif

#include "type_id_map_t_cnt"

//Now `type <=> id` link definition became very short
AUTO_TYPE_ID(int)
AUTO_TYPE_ID(double)
AUTO_TYPE_ID(float)

//Use forward declaration of a template and a specialization with null_t
//to define special base type with ID of the template
template<class T> struct tmpl_id;
template<> struct tmpl_id<ns_type_ids::null_t>;

//Now "null template" is known for the compiler
AUTO_TYPE_ID(tmpl_id<ns_type_ids::null_t>)

//The "null template" specialization
//Realy _BaseT type alias it the "null template" specialization
template<> struct tmpl_id<ns_type_ids::null_t> {
    //returns the same base ID for every class instance
    typedef tmpl_id<ns_type_ids::null_t> _BaseT;
    //generating ID and defining its container
    typedef ns_type_ids::hash<ns_type_ids::ID_by_T<_BaseT>::ID> _Hash;
    //This is the ID of template tmpl_id
    enum {ID=_Hash::res};
};

//Now the target template can be defined.
//tmpl_id<ns_type_ids::null_t> is the base type for all template instances.
//_BaseT is inherited from the base type.
template<class T>
struct tmpl_id: tmpl_id<ns_type_ids::null_t> {
    //unique rarefied calculated ID for every class instance
    typedef ns_type_ids::hash<        
        ns_type_ids::get_id<T>::res
    ,   _BaseT::ID // it is already hash value
                   // and the second calling hash with it is not needed
    > _Hash;
    enum {ID=_Hash::res};
};

int _tmain(int argc, _TCHAR* argv[]) {
    using namespace std;
    using namespace ns_type_ids;

    typedef int int_alias; //for testing behaviour on alias of int
    //Now get_id is used instead of direct access with ID_by_T
#define out_id(type_name) \
    _T("ID_by_T<") _T(#type_name) _T(">::ID: ") << get_id<type_name>::res
#define out_name(type_id) \
    _T("T_by_ID<") _T(#type_id) _T(">: ") << T_by_ID<type_id>::name()

    _tcout
        << _T("ID_by_T -- getting ID of type") << endl
        << out_id(null_t) << endl
        << out_id(int) << endl
        <<_T("ID_of_T<type_alias> works as expected") << endl
        << out_id(int_alias) << endl
        << out_id(double) << endl
        << out_id(float) << endl
        << out_id(tmpl_id<null_t>) << endl
        << out_id(tmpl_id<int>) << endl
        << out_id(tmpl_id<double>) << endl
        << out_id(tmpl_id<float>) << endl
        /* Next commented line generates an error to indicate
           absence of ID for the char type */
        //<< out_id(tmpl_id<char>) << endl 
        << endl
        << _T("T_by_ID -- getting type or its name by ID") << endl
        << out_name(-1) << endl
        << out_name(0) << endl
        << out_name(1) << endl
        << out_name(2) << endl
        << out_name(3) << endl
        << out_name(4) << endl
        << out_name(5) << endl
    ;
    return 0;
#undef out_id
#undef out_name
}

Вывод:

ID_by_T -- getting ID of type
ID_by_T<null_t>::ID: 0
ID_by_T<int>::ID: 1
ID_of_T<type_alias> works as expected
ID_by_T<int_alias>::ID: 1
ID_by_T<double>::ID: 2
ID_by_T<float>::ID: 3
ID_by_T<tmpl_id<null_t>>::ID: 4
ID_by_T<tmpl_id<int>>::ID: 225874304
ID_by_T<tmpl_id<double>>::ID: 225874307
ID_by_T<tmpl_id<float>>::ID: 225874306

T_by_ID -- getting type or its name by ID
T_by_ID<-1>: unassigned
T_by_ID<0>: null_t
T_by_ID<1>: int
T_by_ID<2>: double
T_by_ID<3>: float
T_by_ID<4>: tmpl_id<ns_type_ids::null_t>
T_by_ID<5>: unassigned

Если вы знаете, как вычислять последовательные идентификаторы для экземпляров шаблона, пожалуйста, дайте мне знать, чтобы переписать ns_type_ids::hash: -)

Ответ 3

Я думаю, что то, о чем вы просите, в С++ практически невозможно. Счетчик не может быть известен во время компиляции, потому что отдельные единицы компиляции не знают друг о друге, поэтому вы почти ничего не скрываете.

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

RuntimeID.h

//-----------------------------------------------
class CNextRuntimeID 
{
protected:
  static long m_NextRuntimeID;
};

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

template<class T>
class CIntegerRuntimeTypeID: public CNextRuntimeID
{
  static const long m_RuntimeID;
public:
  inline static long GetRuntimeID()
  {
    return m_RuntimeID;
  }
};

template<class T> 
const long CIntegerRuntimeTypeID<T>::m_RuntimeID = CNextRuntimeID::m_NextRuntimeID++;

RuntimeID.cpp

long CNextRuntimeID::m_NextRuntimeID = 0;

Я немного подумал об этой реализации, и я считаю, что это безопасно. Потенциальная проблема заключается в том, что m_NextRuntimeID теоретически может быть инициализирован до нуля после того, как один из m_RuntimeIDs, что, очевидно, приведет к дублированию значений. Однако в VisualStudio, по крайней мере, инициализация до нуля не генерирует код, тогда как инициализация на основе счетчика делает.

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