【发布时间】:2015-08-27 05:47:19
【问题描述】:
我一直在我的代码中使用此 SO 问题中描述的模式来制作各种编译时注册列表:C++ type registration at compile time trick
例如,如果你有一堆 lua 回调函数并且你不想忘记用一些 lua 状态注册它们,你可以使用一个宏来声明它们,该宏将知道它们的名称和函数指针的模板类型放入一个列表,然后你有一个单行,它注册了所有的功能。
此技术的一个限制(如 SO 答案中所述)是,如果列表中有 n 项目,则需要模板递归深度 O(n) 来评估。这不太理想,我其实已经有不少lua回调函数了……
我曾认为 O(n) 递归深度由于各种原因是不可避免的,但是正如我最近从 Yakk 在这个(不相关的)答案中了解到的那样,我天真地认为需要 O(n) 的一些基本事情实际上可以在 @ 中完成987654327@ 深度。 Switch statement variadic template expansion
尤其是,没有理由再要求所涉及的数据结构的操作需要O(log n) 模板深度。
我不确定的部分是Rank 技巧。从引用的代码,这个模板
template <int N>
struct Rank : Rank<N - 1> {};
template <>
struct Rank<0> {};
是一个关键成分。但是,就模板深度而言,它的成本很高——实例化Rank<N> 需要模板深度N。但是,它具有的重要属性是,如果定义了使用许多不同等级类型重载的函数f,则重载决策总是选择最大等级的重载,因为那是“最派生的实例”。例如。如果我们有这个代码:
bool f(Rank<0>) { return false; }
int f(Rank<1>) { return 0; }
float f(Rank<2>) { return 0; }
double f(Rank<3>) { return 0; }
那么在代码中的任何一点,decltype(f(Rank<100>{})) 的类型总是等于最近定义的重载的返回值。 IE。这些断言通过了
bool f(Rank<0>) { return false; }
static_assert(std::is_same<bool, decltype(f(Rank<100>{}))>::value, "D:");
int f(Rank<1>) { return 0; }
static_assert(std::is_same<int, decltype(f(Rank<100>{}))>::value, "D:");
float f(Rank<2>) { return 0; }
static_assert(std::is_same<float, decltype(f(Rank<100>{}))>::value, "D:");
double f(Rank<3>) { return 0; }
static_assert(std::is_same<double, decltype(f(Rank<100>{}))>::value, "D:");
有没有不需要模板递归深度的方法O(n)?
也许为函数重载使用更多、仔细选择的参数 (?)
【问题讨论】:
-
您测量拍摄的内容了吗?作为总时间的一部分,它对您重要吗?还是出于“学术”兴趣?
-
@TonyD:我真正关心的不是时间,而是达到最大模板递归深度(通常为 256)和编译过程出错。
-
我在一些地方使用了这种模式——如果限制相当大就好了,比如数百万而不是数百,所以我不必跟踪这些东西。除了“学术”兴趣之外,获得这一点比获得
O(log n)更重要。 -
这项工作的关键在于
Rank<M>是所有M < N的Rank<N>的直接或间接基类。你无法真正避免线性实例化。 -
@ChrisBeck 我提到了运行时性能,因为你表面上会不遗余力地使用编译时技术,而不是让你的宏使用注册单个函数的构造函数创建一些静态对象,或者添加用于 post-
main()注册的标准库容器的名称和函数指针。您调用哪种注册函数可以更好地处理编译时(函数指针)类型信息?也许如果我们能看到你最终调用的 lua 注册码,我们可以欣赏/评估需求或好处....
标签: c++ templates c++11 recursion c++14