模板类的编译时计数器 [英] compile-time counter for template classes

查看:169
本文介绍了模板类的编译时计数器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设你有很多类,有很多不同的模板参数。每个类都有一个方法 static void f()。你想在列表L中收集所有这些函数指针。



运行时解决方案很容易:

  typedef void(* p)(); 
std :: vector< p> L;
int reg(p x){static int i = 0; L.push_back(x); return i ++; } //也返回唯一的id

template< typename T> struct regt {static int id; };
template< typename T> int regt T :: id = reg(T :: f);

template< typename ... T> struct class1:regt< class1< T ...> > {static void f(); };
template< typename ... T> struct class2:regt< class2< T ...> > {static void f(); };
//等

编译器知道所有 f 所有实例化的类在编译时。因此,理论上应该可以生成这样的列表( const std :: array< p,S> L with some S )作为编译时常量列表。但是怎么样?欢迎使用C ++ 0x解决方案。






为什么需要?



在只有256 kB(对于代码和数据)的架构上,我需要为类的传入ID生成对象。现有的序列化框架或上述运行时解决方案不必要地大。没有模板,编译时解决方案很容易,但我想保留模板提供的所有优点。

解决方案

手动



您最简单的方法就是手动滚动代码,我不认为有太多可以用于您的优势从模板,所以我将使用纯类,其中 A B ...代表您的类型。这允许类型的编译时初始化,代价是每当将新类型添加到系统时必须记住更新查找表:

  typedef void(* function_t)(); 
function_t func [] = {
& A :: f,
& B :: f,
& C :: f
}



从维护的角度来看,我会推荐这个。自动化系统会使代码在将来难以理解和维护。





简单的最自动化的代码,它可能会产生更少的代码是一个宏生成系统只是使用宏。因为这第一种方法将使用宏的大量使用,我将自动生成函数,就像你在上一个问题。如果您(希望)通过宏指定了完整代码生成的路径,您可以删除该部分代码。



为避免重新键入类型的名称不同的上下文你可以定义一个宏与所有上下文需要的所有数据,然后使用其他宏来过滤每个特定上下文中使用(以及如何):

  //这是所有类型,id和代码的实际列表
//在静态函数的其他问题中生成:
#define FOREACH_TYPE(macro)\
macro(A,0,{std :: cout<A;})\
宏(B,1,{std :: cout 宏(C,2,{std :: cout<C;})

//现在我们使用递归宏:
//创建一个枚举并计算所使用的类型数量
#define ENUM_ITEM(type,id,code)\
e _ ## type,
enum AllTypes {
FOREACH_TYPE(ENUM_ITEM)
AllTypes_count
};
#undef ENUM_ITEM

//现在我们可以创建一个函数指针数组
typedef void(* function_t)();
function_t func [AllTypes_count];

//我们可以创建所有类:
#define CREATE_TYPE(type,the_id,code)\
struct type {\
static const int id = the_id; \
static void func()code\
};
FOREACH_TYPE(CREATE_TYPE)
#undef CREATE_TYPE

//创建一个函数
#define REGISTER_TYPE(type,id,code)\
func [i ++] =& type :: func;

void perform_registration(){
int i = 0;
FOREACH_TYPE(REGISTER_TYPE);
};
#undef REGISTER_TYPE

//现在我们可以测试它
int main(){
perform_registration();
for(int i = 0; i func [i]();
}
}

这是另一方面的维修噩梦,相当脆弱,很难调试。添加新类型是微不足道的,只需在 FOREACH_TYPE 宏中添加一行,即可完成...并且一旦发生故障,运气最好!



模板和元编程



另一方面,使用模板可以接近但不能得到类型的单点定义。您可以以不同的方式自动化某些操作,但至少需要定义类型本身并将它们添加到类型列表以获取其余功能。



使用C ++ 0x代码简化实际的type_list的定义,您可以先定义类型,然后创建 type_list 。如果你想避免使用C ++ 0x,那么看看Loki库,但是使用C ++ 0x类型列表很简单:

  template< typename ... Args> type_list {}; // generic type list 
typedef type_list< A,B,C,D>类型; //我们的类型A,B,C和D的具体列表
//这是唯一的重复源:
// types必须定义并手动添加到
// type_list [*]



现在我们需要使用一些元编程来操作类型列表,计数列表中的元素数:

  template< typename List>结构大小; // declare 
template< typename T,typename ... Args> // general case(recursion)
struct size< type_list< T,Args ...> > {
static const int value = 1 + size< type_list< Args ...> :: value;
};
模板<> //停止递归的条件
struct size< type_list<> > {
static const int value = 0;
};

拥有类型列表的大小是我们问题的第一步,因为它允许我们定义一个函数数组:

  typedef void(* function_t) //每个函数指针的签名
struct registry {
static const int size = :: size< types> :: value;
static const function_t table [size];
};
function_t registry :: table [registry :: size]; //定义指针数组

现在我们要注册每个特定类型的静态函数数组,为此,我们创建一个辅助函数(封装为类型中的静态函数,以允许部分特化)。注意,这个具体部分被设计为在初始化期间运行:它不会是编译时间,但是成本应该是微不足道的(我会更担心所有模板的二进制大小):

  template< typename T,int N> // declaration 
struct register_types_impl;
template< typename T,typename ... Args,int N> // general recursion case
struct register_types_impl< type_list< T,Args ...>,N> {
static int apply(){
registry :: table [N] =& T :: f; //寄存器函数指针
return register_types_impl< type_list< Args ...>,N + 1> ;;
}
};
template< int N> // stop condition
struct register_types_impl< type_list<>,int N> {
static int apply(){return N; }
};
//和一个更好的接口:
int register_types(){
register_types_impl<类型,0>();
}



现在我们需要一个id函数,将我们的类型映射到函数指针在我们的例子中是类型列表中的类型的位置

  template< typename T,typename List,int N> // same old,same old ... declaration 
struct id_impl;
template< typename T,typename U,typename ... Args,int N>
struct id_impl< T,type_list< U,Args ...>,N> {// general recursion
static const int value = id_impl< T,type_list< Args ...>,N + 1>
};
template< typename T,typename ... Args,int N> //停止条件1:找到类型
struct id_impl< T,type_list< T,Args ...>,N> $ {
static const int value = N;
};
template< typename T,int N> // stop condition 2:type not found
struct id_impl< T,type_list,N> {
static const int value = -1;
}
//和一个干净的接口
模板< typename T,typename List>
struct id {
static const int value = id_impl< T,List,0> :: value;
};

现在,您只需要在运行时触发注册,然后再执行任何其他代码:

  int main(){
register_types(); //这将构建查找表
}

sup>好吧,你可以使用宏技巧重用这些类型,因为宏的使用是有限的,它不会那么难以维护/调试。


Imagine that you have a lot of classes with a lot of different template parameters. Every class has a method static void f(). You want to collect all these function pointers in a list L.

A run-time solution would be easy:

typedef void (*p)();
std::vector<p> L;
int reg (p x) { static int i = 0; L.push_back(x); return i++; } // also returns an unique id

template <typename T> struct regt { static int id; };
template <typename T> int regt<T>::id = reg (T::f);

template < typename ... T > struct class1 : regt< class1<T...> > { static void f(); };
template < typename ... T > struct class2 : regt< class2<T...> > { static void f(); };
// etc.

The compiler knows all f()s of all instantiated classes at compile-time. So, theoretically it should be possible to generate such a list (a const std::array<p, S> L with some S) as a compile-time constant list. But how? (C++0x solutions are welcome, too).


Why do I need this?

On an architecture with only 256 kB (for code and data), I need to generate objects for incoming ids of classes. Existing serialization frameworks or the run-time solution above are unnecessarily big. Without templates a compile-time solution would be easy, but I want to keep all the advantages templates offer.

解决方案

Manually

The simplest thing that you can do is just roll the code manually, I don't think that there is much that can be used to your advantage from the templates, so I will use plain classes, where A, B... stand for particular instantiations of your types. That allows for compile time initialization of the types, at the cost of having to remember to update the lookup table whenever a new type is added to the system:

typedef void (*function_t)();
function_t func[] = {
    &A::f,
    &B::f,
    &C::f
};

I would recommend this, from a maintenance point of view. Automating the system will make the code much harder to understand and maintain in the future.

Macros

The simple most automated one, which will probably generate less code is a macro generation system is just using macros. Since this first approach will use extensive use of macros, I will generate the functions automatically, as you did in the previous question. You can remove that part of code if you have (hopefully) given up the path of full code generation through macros.

To avoid having to retype the names of the types in different contexts you can define a macro with all the data you need for any context, and then use other macros to filter what is to be used (and how) in each particular context:

// This is the actual list of all types, the id and the code that you were
// generating in the other question for the static function:
#define FOREACH_TYPE( macro ) \
    macro( A, 0, { std::cout << "A"; } ) \
    macro( B, 1, { std::cout << "B"; } ) \
    macro( C, 2, { std::cout << "C"; } )

// Now we use that recursive macro to:
// Create an enum and calculate the number of types used
#define ENUM_ITEM( type, id, code ) \
    e_##type,
enum AllTypes {
    FOREACH_TYPE( ENUM_ITEM )
    AllTypes_count
};
#undef ENUM_ITEM

// Now we can create an array of function pointers
typedef void (*function_t)();
function_t func[ AllTypes_count ];

// We can create all classes:
#define CREATE_TYPE( type, the_id, code ) \
struct type {\
   static const int id = the_id; \
   static void func() code\
};
FOREACH_TYPE( CREATE_TYPE )
#undef CREATE_TYPE

// And create a function that will 
#define REGISTER_TYPE( type, id, code ) \
    func[ i++ ] = &type::func;

void perform_registration() {
   int i = 0;
   FOREACH_TYPE( REGISTER_TYPE );
};
#undef REGISTER_TYPE

// And now we can test it
int main() {
   perform_registration();
   for ( int i = 0; i < AllTypes_count; ++i ) {
      func[ i ]();
   }
}

This is, on the other hand a maintenance nightmare, quite fragile and hard to debug. Adding new types is trivial, just add a new line to the FOREACH_TYPE macro and you are done... and the best of lucks once something fails...

Templates and metaprogramming

On the other hand, using templates you can get close but you cannot get to the single point of definition for the types. You can automate some of the operations in different ways, but at the very least you will need to define the types themselves and add them to a typelist to get the rest of the functionality.

Simplifying the definition of the actual type_list with C++0x code you can start by defining the types and then creating the type_list. If you want to avoid using C++0x, then take a look at the Loki library, but with C++0x a type list is simple enough:

template <typename ... Args> type_list {}; // generic type list
typedef type_list< A, B, C, D > types;     // our concrete list of types A, B, C and D
                                           // this is the only source of duplication:
                                           // types must be defined and added to the
                                           // type_list manually [*]

Now we need to use some metaprogramming to operate on the type list, we can for example count the number of elements in the list:

template <typename List> struct size;     // declare
template <typename T, typename ... Args>  // general case (recursion)
struct size< type_list<T,Args...> > {
   static const int value = 1 + size< type_list<Args...>::value;
};
template <>                               // stop condition for the recursion
struct size< type_list<> > {
   static const int value = 0;
};

Having the size of the type list is a first step in our problem, as it allows us to define an array of functions:

typedef void (*function_t)();                 // signature of each function pointer
struct registry {
   static const int size = ::size< types >::value;
   static const function_t table[ size ];
};
function_t registry::table[ registry::size ]; // define the array of pointers

Now we want to register the static functions from each particular type in that array, and for that we create an auxiliar function (encapsulated as a static function in a type to allow for partial specializations). Note that this concrete part is designed to be run during initialization: it will NOT be compile time, but the cost should be trivial (I would be more worried on the binary size with all the templates):

template <typename T, int N>                         // declaration
struct register_types_impl;
template <typename T, typename ... Args, int N>      // general recursion case
struct register_types_impl< type_list<T,Args...>, N> {
   static int apply() {
      registry::table[ N ] = &T::f;                  // register function pointer
      return register_types_impl< type_list<Args...>, N+1 >;
   }
};
template <int N>                                     // stop condition
struct register_types_impl< type_list<>, int N> {
   static int apply() { return N; }
};
// and a nicer interface:
int register_types() {
   register_types_impl< types, 0 >();
}

Now we need an id function that maps our types to the function pointer, which in our case is the position of the type in the type list

template <typename T, typename List, int N>      // same old, same old... declaration
struct id_impl;
template <typename T, typename U, typename ... Args, int N>
struct id_impl< T, type_list<U, Args...>, N > {  // general recursion
   static const int value = id_impl< T, type_list<Args...>, N+1 >;
};
template <typename T, typename ... Args, int N>  // stop condition 1: type found
struct id_impl< T, type_list<T, Args...>, N> {  
   static const int value = N;
};
template <typename T, int N>                     // stop condition 2: type not found
struct id_impl< T, type_list<>, N> {
   static const int value = -1;
}
// and a cleaner interface
template <typename T, typename List>
struct id {
   static const int value = id_impl<T, List, 0>::value;
};

Now you just need to trigger the registration at runtime, before any other code:

int main() {
   register_types(); // this will build the lookup table
}

[*] Well... sort of, you can use a macro trick to reuse the types, as the use of macros is limited, it will not be that hard to maintain/debug.

这篇关于模板类的编译时计数器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆