模板类的编译时计数器

发布于 2024-10-31 08:36:32 字数 901 浏览 2 评论 0原文

想象一下,您有很多带有很多不同模板参数的类。每个类都有一个方法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++; } // 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.

编译器在编译时知道所有实例化类的所有 f()。因此,理论上应该可以生成这样一个列表(一个 const std::array

L 和一些 S)作为编译时常量列表。但如何呢? (也欢迎 C++0x 解决方案)。


为什么我需要这个?

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

通知家属抬走 2024-11-07 08:36:32

手动

您可以做的最简单的事情就是手动滚动代码,我不认为模板可以为您带来太多好处,所以我将使用普通类,其中AB...代表类型的特定实例。这允许在编译时初始化类型,但代价是每当将新类型添加到系统时必须记住更新查找表:

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

从维护的角度来看,我建议这样做。系统自动化将使代码将来更难理解和维护。

最简单、最自动化的宏生成系统可能会生成更少的代码,它只使用宏。由于第一种方法将大量使用宏,因此我将自动生成函数,就像您在上一个问题中所做的那样。如果您(希望)放弃了通过宏生成完整代码的路径,则可以删除该部分代码。

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

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

:另一方面,这是一场维护噩梦,非常脆弱且难以调试。添加新类型很简单,只需向 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 > 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 [*]

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

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

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

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

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

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

现在我们需要一个映射的 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>  // 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;
};

现在你只需要在运行时触发注册,在任何其他代码之前:

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

[*] 好吧..某种程度上,您可以使用宏技巧来重用类型,因为宏的使用是有限的,所以维护/调试不会那么困难。

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.

夏末的微笑 2024-11-07 08:36:32

编译器在编译时知道所有实例化类的所有 f()

这就是你的错误。编译器对其他编译单元中的模板实例化一无所知。现在应该很明显为什么实例化的数量不是可以用作模板参数的常量整数表达式(如果 std::array 是专门化的呢?前面的问题停止了!)

The compiler knows all f()s of all instantiated classes at compile-time.

There's your mistake. The compiler knows nothing about template instantiations in other compilation units. It should now be pretty obvious why the number of instantiations isn't a constant integral expression that could be used as a template argument (and what if std::array was specialized? Halting Problem ahead!)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文