【问题标题】:compile-time counter for template classes模板类的编译时计数器
【发布时间】:2011-04-11 15:47:57
【问题描述】:

想象一下,您有很多具有很多不同模板参数的类。每个类都有一个方法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()s。因此,理论上应该可以生成这样一个列表(const std::array&lt;p, S&gt; L 和一些S)作为编译时常量列表。但是怎么做? (也欢迎使用 C++0x 解决方案)。


我为什么需要这个?

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

【问题讨论】:

  • 据我了解,这是不可能的,因为您无法在编译时修改任何内容,因此您将无法构建您的列表。然而,为了简化这个过程,您可以使用一个“神奇的”基类来进行注册。
  • 这种功能的用例是什么?
  • @Oli:我需要为所有类分配唯一的 id(出于序列化目的),并在给定 id 时生成它们的实例。这是一个非常低内存的架构,所以我不想使用现有的序列化框架并且运行时解决方案的reg-calls 会生成不必要的代码。
  • @filmor:根据你的建议修改了代码
  • 我想从今天开始,您的另一个问题中可能会有answered this already,就在您写这个新问题的前几分钟。看看它,如果它不符合您的要求,请随时询问任何细节。

标签: c++ templates c++11


【解决方案1】:

手动

您可以做的最简单的事情就是手动滚动代码,我认为模板没有太多可以利用的优势,所以我将使用普通类,其中A,@ 987654322@... 代表您的类型的特定实例化。这允许在编译时对类型进行初始化,但代价是每次向系统添加新类型时都必须记住更新查找表:

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
}

[*] 嗯...有点,你可以使用宏技巧来重用类型,因为宏的使用是有限的,维护/调试不会那么难。

【讨论】:

  • 感谢您的详细解答。我稍微简化了您的模板解决方案:template &lt; typename ... T &gt; std::array &lt; void(*)(), sizeof...(T) &gt; register_types () { return {{ T::f... }}; } auto table = register_types&lt; A, B, C, D &gt; (); -- 不过,剩下的(大)问题是很难猜测给定应用程序将使用所有可能的模板实例化(当然,我们不想要为所有可能性生成代码)。因此,手动生成可能由模板实例化产生的所有类型的列表是不明智的。
  • @Thomas:您可以尝试以不同的方式解决它:不提供模板定义,只提供声明。这样,一切都将编译并且无法链接。然后在单个 cpp 文件中,包含模板定义,并使用显式实例化。在该类中,您在register_types 中添加类型列表。这将需要手动工作,但如果您忘记实例化类型,至少编译器(或者更确切地说是链接器)会帮助您,虽然类型列表是手动的,但它位于单个文件中。
  • 关于解决方案的复杂性...这是我第一次使用 C++11 可变参数模板,我想到了一个使用类型列表的解决方案,如现代 C++ 设计中所示,刚刚删除一堆带有可变参数模板的样板。我需要进入 C++11 思维模式! (感谢您将解决方案添加为评论)
【解决方案2】:

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

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

【讨论】:

  • @Ben:我明白了,但在我的情况下,只需要单个编译单元的解决方案。
  • @Thomas:您仍在尝试解决停机问题。如果用于“列表”变量的类型本身导致使用更多模板怎么办?
  • @Ben:你正在尝试解决一个比我更普遍的问题——我不会序列化函数指针列表,所以我不需要它的类拥有f其地址包含在列表中的方法。我看到原始形式的问题陈述过于笼统,但我添加了我的用例。
  • @Thomas:所以你的案子很快就会停止。但这仍然很难看,因为您仍然希望编译器主要编译代码,列出所有模板实例,然后实例化更多模板(在您的情况下为std::array)。 C++ 回避了这个问题,但没有提供在编译期间列出模板实例化的任何方法。您可以通过添加从源代码创建列表的预编译工具或从调试数据库创建列表的后处理工具来引入更多阶段,或者包含在运行时构建列表的代码。
  • @Ben:你是对的:这可能需要模板实例化的两个阶段。但是,使用运行时解决方案,编译器生成臃肿的代码以动态地将数百个指针插入到向量中并可能在此过程中调整它的大小(当你只有一些 kB 时,malloc 真的很糟糕),这不是更丑陋吗,而理想情况下是简单的查找表就足够了吗?而且手动预处理或后处理更难看……
猜你喜欢
  • 2015-10-21
  • 1970-01-01
  • 2015-09-15
  • 1970-01-01
  • 1970-01-01
  • 2016-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多