消除C++中的递归模板实例化

Ari*_*Ari 10 c++ templates template-specialization

我想定义一个可以在不同位置(在文件范围内)调用的宏,以便创建执行某些操作的函数.(在下面的例子中,函数只是打印一条消息,但当然我的真正意图是做一些其他有用的东西.)挑战是我想要一些"经理"功能(在我的例子中它只会是main())以某种方式成功在所有调用它们(以任何顺序),没有任何代码依赖于宏调用(当然,除了宏调用本身).我的意思是,一旦编写了文件,另一个程序员就能够在不同的地方插入一些新的宏调用或者删除一些现有的调用,代码仍然可以在没有任何进一步改变的情况下工作.我意识到这可以使用静态对象完成,但我想探索一种不同的方法.我将使用一些模板技巧和__LINE__单调增加的事实.

#include <iostream>
using namespace std;

template<int i>
inline void f()
{
   f<i-1>();
}

#define START_REGISTRATION                                \
template<>                                                \
inline void f<__LINE__>() {}  /* stop the recursion */    \
template<> void f<__LINE__>()  /* force semicolon */

#define REGISTER(msg)                                     \
template<>                                                \
inline void f<__LINE__>()                                 \
{                                                         \
   cout << #msg << endl;                                  \
   f<__LINE__ - 1>();                                     \
}                                                         \
template<> void f<__LINE__>()  /* force semicolon */

// Unrelated code ...

START_REGISTRATION;

// Unrelated code ...

REGISTER(message 1);

// Unrelated code ...

REGISTER(message 2);

// Unrelated code ...

REGISTER(message 3);

// Unrelated code ...

// manager function (in this case main() )
int main()
{
   f<__LINE__>();
}
Run Code Online (Sandbox Code Playgroud)

这打印

message 3
message 2
message 1
Run Code Online (Sandbox Code Playgroud)

正如所料.

该解决方案有一些缺点.

  1. 无法REGISTER在同一行上调用两次.
  2. 如果#line玩的话会打破.
  3. 需要在所有调用之后放置管理器REGISTER.
  4. 由于递归实例化而增加了编译时间.
  5. 除非所有"虚拟"实例f都被内联,否则运行时的调用堆栈深度将与管理器之间START_REGISTRATION;和 之间的行数一样大f<__LINE__>();.
  6. 代码膨胀:除非"虚拟"实例f都被内联,实例化的数量同样会很大.
  7. 过多的实例化递归深度可能会达到编译器的限制(默认情况下在我的系统上为500).

问题1-4我真的不介意.通过让每个函数返回指向前一个函数的指针,让管理器使用这些指针迭代地调用函数而不是让它们相互调用,可以消除问题5.可以通过创建类似的类模板构造来消除问题6,该构造能够计算REGISTER在先前调用中实例化的函数的每次调用,从而仅实例化实际执行某些操作的函数.然后,过多的实例化将从函数模板转移到类模板,但是类实例化只会对编译器产生负担; 它们不会触发任何代码生成.所以我真正关心的是问题7,问题是:有没有办法重构事物,以便编译器迭代地而不是递归地执行实例化.我也对不同的方法持开放态度(除了那些涉及静态对象的方法).一个简单的解决方案是在管理器之前将所有注册组合在一起(或者添加一个STOP_REGISTRATION宏来结束注册块),但这会破坏我目的的重要部分(在定义它的代码旁边注册东西).

编辑: 有一些有趣的建议,但我担心我没有在我希望实现的目标方面做出明确的准备.我真的对两件事情感兴趣:解决问题(即没有静态,每次注册单行,添加/删除注册时没有其他更改,虽然我忽略了这样说,但只有标准C++ ---因此,没有提振).正如我在下面的评论中所说,我的兴趣更具理论性:我希望学习一些新技术.因此,我真的希望专注于重组事情,以便消除(或至少减少)递归或找到满足我上面列出的约束的不同方法.

编辑2: MSalter的解决方案向前迈出了一大步.起初我认为每次注册都会产生线路的全部成本,但后来我意识到一个函数当然只能实例化一次,所以在实例化方面我们付出与线性搜索相同的价格,但是递归深度变为对数.如果我接触它,我将发布一个完整的解决方案,消除问题5-7.但是,看看它是否可以在恒定的递归深度中完成仍然会很好,最好的是,实例化的数量与调用的数量成线性关系(a-la boost解决方案).

编辑3: 这是完整的解决方案.

#define START_REGISTRATION                                          \
template<int lo, int hi>                                            \
struct LastReg {                                                    \
  enum {                                                            \
     LINE_NUM = LastReg<(lo + hi)/2 + 1, hi>::LINE_NUM ?            \
        static_cast<int>(LastReg<(lo + hi)/2 + 1, hi>::LINE_NUM) :  \
        static_cast<int>(LastReg<lo, (lo + hi)/2>::LINE_NUM)        \
  };                                                                \
};                                                                  \
template<int l>                                                     \
struct LastReg<l, l> {                                              \
   enum { LINE_NUM = 0 };                                           \
};                                                                  \
template<int l>                                                     \
struct PrevReg {                                                    \
   enum { LINE_NUM = LastReg<__LINE__ + 1, l - 1>::LINE_NUM };      \
};                                                                  \
template<int l> void Register() {}                                  \
template<int l> void Register()  /* force semicolon */


#define REGISTER(msg)                                               \
template<>                                                          \
struct LastReg<__LINE__, __LINE__> {                                \
   enum { LINE_NUM = __LINE__ };                                    \
};                                                                  \
template<>                                                          \
void Register<__LINE__>()                                           \
{                                                                   \
   cout << __LINE__ << ":" << #msg << endl;                         \
   Register<PrevReg<__LINE__>::LINE_NUM>();                         \
}                                                                   \
template<> void Register<__LINE__>()  /* force semicolon */


#define END_REGISTRATION                                            \
void RegisterAll()                                                  \
{                                                                   \
   Register<PrevReg<__LINE__>::LINE_NUM>();                         \
}                                                                   \
void RegisterAll()  /* force semicolon */


START_REGISTRATION;

REGISTER(message 1);

REGISTER(message 2);

END_REGISTRATION;


int main()
{
   RegisterAll();
}
Run Code Online (Sandbox Code Playgroud)

MSa*_*ers 5

您遇到的问题是您正在进行线性搜索f<i>,这会导致过多的实例化.

解决方案是f<i>打电话g<i,0>.这反过来又来电g<i,i/2>g<i/2,0>,其中要求g<i,i/2+i/4>,g<i/2+i/4,i/2>,g<i/2,i/4>g<i/4, 0>ectetera.你当然会专注g<__LINE__, __LINE__>于内部REGISTER().

实例化f<65536>仍会导致65536个模板实例化(您有效地检查所有先前的65536行),但递归深度限制为log(65536)或16个级别.这是可行的.