Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
565 views
in Technique[技术] by (71.8m points)

c++ - How can I generate dense unique type IDs at compile time?

I'm trying to make a system of classes that are small objects, and the base class has a member that is a unique identifier that identifies the class:

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

With the class identifier, I go through a vector of Shape * and switch on the id visible in the base class, then static cast for different behavior (to Triangle, Square, or ShapeBox and child shapes held in it respectively for the example class hierarchy)

I could turn on RTTI, but the space cost seems fairly large, especially when the type information can be implemented as a pointer and the object size might be no bigger than a couple of bytes. There may be millions of small objects, and I really only need static cast anyways.

Currently I can make type identifiers by using statics that are assigned values from a static monotonically incrementing counter:

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;

Ideally I want dense, unique type ID's that are available at compile time, so the compiler can perform well, like converting a switch on the type IDs into a constant time jump table, or at least a binary search tree rather than a linear time if/else chain for what might be a long list of type IDs...

I can use boilerplate at compile time to manually assign every type ID, or I can use object/function pointers from a similar type ID class. Boiler plate is guaranteed to be dense (because we assign it manually) and known at compile time, but it's unmaintainable for template types. Whenever you add a template type to a shape, you have to manually add a new type. The monotonic static counter is maintainable and dense, but unknown at compile time and so compile time optimizations aren't possible, and thread safety may be a concern. The function pointer ID is known at compile time and maintainable, but isn't dense and won't fit into a small id type like a char.

Is there any way to generate type IDs that are visible to the compiler at compile time, dense, and automatically assigned, perhaps using template metaprogramming counter or some preprocessor magic in C++11 or C++14? Or is this not possible until C++ has compile time reflection?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Is there any way to generate type IDs that are visible to the compiler at compile time, dense, and automatically assigned, perhaps using template metaprogramming counter

I've developed a code which does this with few restrictions. Two template specializations ID_by_T and T_by_ID define type <=> ID link at compile time. Type's ID is a enum constant. If type <=> ID link is not defined ID_by_T<type>::ID is -1 and T_by_ID<undefinedID>::type is null_t predefined type. DEF_TYPE_ID(type_name) macro generates new ID when defines type <=> ID link. int_triangle and char_triangle show how to get typedef with correct type's ID and inner typedef _MyID_T shows how to get ID of type. The code was compiled with MS VS 2005 C++.

The core -- type_id_map header file:

#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

And the use of type_id_map example in 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
}

Output:

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>

Requirements and restrictions:

  1. Type <=> ID map is global and works only at compile time.
  2. Type <=> ID link must be defined at global namespace level using DEF_TYPE_ID and PREV_TYPE macro.
  3. "IDed" type must be declared prior to defintion of Type <=> ID link.
  4. In order to get self ID inside a class use ID_by_T<self_type> as a base class where self_type is an own type of class. But why (see below)?
  5. In order to get self ID inside a templated class use ID_by_T<base_data_type> as a base class where base_data_type is a special base type of templated class for wich type <=> ID link is already defined. See int_triangle and char_triangle for example. Also there are other tricks to get defined ID inside a template instance.

Features

  • IDs are external and allocated at compile time automatically sequentially beginning with 0 in the compilation order of type <=> ID link definitions. This is inevitability due to the requirement of the question.
  • Minimal requirements to a compiler: only standard features of ISO/IEC 14882:2003 SE.
  • Macros __COUNTER__, __LINE__, BOOST_PP_COUNTER or based on sizeof are not used to allocate ID: there are no side effects associated with them.
  • The type <=> id map is based on external IDs known at compile time:
    • An ID can be assigned to every type, even to a fundamental type.
    • ID_T_pair template describes type <=> id link. ID_by_T/T_by_ID templates are direct descendants of ID_T_pair template.
    • Due to ID_by_T template it is not necessary to define ID inside of a type (what is impossible for fundamental types).
    • ID by type is accessed with ID_by_T<type>::ID enum constant.
    • Type by ID is accessed with T_by_ID<ID>::_Type inner typedef.
    • Optional: type's name is accessed with name() method of ID_T_pair.
    • The map does not occupy any memory byte if method name() of ID_T_pair is not used.
  • The map is distributed: type <=> id link can be defined in place but at global namespace level.
  • In order to access the map in a few TU a special header may be used.
  • Definition of a composed derived type does not require any additional info for the map.
  • The map uses special null type null_t and ID=-1 returned in absence of type <=> ID link.

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...