F考虑一个带constexpr size_t参数的函数对象I
struct F
{
template <size_t I>
constexpr size_t operator()(size <I>) const { return I; }
};
包裹在 typesize <I>中,其中(为简洁起见)
template <size_t N>
using size = std::integral_constant <size_t, N>;
当然,我们可以I直接传递,但我想强调它是 constexpr,将其用作模板参数。函数F在这里是虚拟的,但实际上它可以做各种有用的事情,比如从I元组的第 th 元素中检索信息。F假定无论I. I可以是任何整数类型,但假定为非负数。
问题
给定一个constexpr size_t值,I我们可以调用F
F()(size <I>());
现在,如果我们想F用非 consteprsize_t值调用i怎么办?考虑以下:
constexpr size_t L = 10;
idx <F, L> f;
for (size_t i = 0; i < L; ++i)
cout << f(i) << " ";
(我为什么需要这个?为了给出一些上下文,我实际上是在尝试将复合迭代器构建到一个容器视图中,该视图表示一系列“连接”(连接)异构容器。这将能够说出类似join(a, b) = c;where数组join(a, b)和c长度相等。但是,i迭代器状态是不是这样constexpr,但子迭代器存储在元组中,需要通过constexpr索引访问。个体value_type的大致一致,因此连接视图可以采用它们的 common_type类型,但是子容器和子迭代器是不同的类型。)
解决方案
在这里,我提出了 struct idx <F, L>,它为此目的调整函数F,假设输入参数小于L. 这实际上可以很好地编译输出
0 1 2 3 4 5 6 7 8 9
这是一个活生生的例子。
idx通过递归地将输入分解i为二进制表示并重建 constexpr 对应项来工作N:
template <typename F, size_t L, size_t N = 0, bool = (N < L)>
struct idx
{
template <size_t R = 1>
inline constexpr decltype(F()(size <0>()))
operator()(size_t I, size <R> = size <R>()) const
{
return I%2 ? idx <F, L, N+R>()(--I/2, size <2*R>()) :
I ? idx <F, L, N >()( I/2, size <2*R>()) :
F()(size <N>());
}
};
其中R表示2当前迭代的幂。为了避免无限的模板实例化,对 进行了专门化N >= L,返回F()(size <0>())一个虚拟值:
template <typename F, size_t L, size_t N>
struct idx <F, L, N, false>
{
template <size_t R>
inline constexpr decltype(F()(size <0>()))
operator()(size_t I, size <R>) const { return F()(size <0>()); }
};
事实上,这种方法是更常见的带有布尔参数的成语的概括:
bool b = true;
b ? f <true>() : f <false>();
其中f是一个将 abool作为模板参数的函数。在这种情况下,很明显所有两个可能的版本f都被实例化了。
问题
尽管这可行,并且它的运行时复杂性可能是对数i,但我担心编译时的影响,例如:
有多少
idx和 它的组合template operator()被实例化,以便在运行时为i编译时未知的任何输入工作?(我再次理解“所有可能”,但有多少?)真的可以内联
operator()吗?是否有任何“更容易”编译的替代策略或变体?
我应该忘记这个想法作为纯代码膨胀的实例吗?
笔记
以下是我针对不同值测量的编译时间(以秒为单位)和可执行文件大小(以 KB 为单位)L:
L Clang(s) GCC(s) Clang(KB) GCC(KB)
10 1.3 1.7 33 36
20 2.1 3.0 48 65
40 3.7 5.8 87 120
80 7.0 11.2 160 222
160 13.9 23.4 306 430
320 28.1 47.8 578 850
640 56.5 103.0 1126 1753
因此,尽管它在 中看起来大致呈线性L,但它相当长且大得令人沮丧。
尝试强制operator()内联失败:可能被 Clang 忽略(可执行文件更大),而 GCC 报告recursive inlining.
在可执行文件上运行nm -C,例如 for L = 160,显示511/1253不同版本的operator()(使用 Clang/GCC)。这些都是为了N < L,所以看起来终止的专业化N >= L确实被内联了。
附言。我会添加标签code-bloat,但系统不会让我。