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
,但系统不会让我。