考虑使用F
参数constexpr size_t
I
struct F
{
template <size_t I>
constexpr size_t operator()(size <I>) const { return I; }
};
包含在size <I>
类型中,其中(为简洁起见)
template <size_t N>
using size = std::integral_constant <size_t, N>;
当然,我们可以直接传递I
,但我想通过使用它作为模板参数强调它是constexpr。函数F
在这里是假的,但实际上它可以做各种有用的东西,比如从元组的I
元素中检索信息。无论F
如何,都假定I
具有相同的返回类型。 I
可以是任何整数类型,但假定为非负数。
问题
给定constexpr size_t
值I
,我们可以通过
F
F()(size <I>());
现在,如果我们想要使用非constepr F
值size_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;
之类的数组join(a, b)
和c
的长度相同。但是,i
是迭代器状态所以不能是constexpr
,而是迭代器存储在一个元组中,需要通过constexpr
索引进行访问。单个value_type
大致一致,以便联合视图可以采用common_type
类型,但是容器和子迭代器的类型不同。)
解决方案
在这里,我提出了结构idx <F, L>
,它为此目的调整函数F
,假设输入参数小于L
。这实际上编译了精细的输出
0 1 2 3 4 5 6 7 8 9
这是一个live example。
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
是一个以bool
为模板参数的函数。在这种情况下,很明显f
的所有两个可能版本都被实例化了。
问题
虽然这有效并且其运行时复杂性可能在i
中是对数的,但我关注编译时的含义,例如:
实例化idx
及其template operator()
的多少个组合,以便在运行时为编译时未知的任何输入i
工作? (我再次理解“所有可能的”,但有多少?)
是否真的可以内联operator()
?
是否存在“更容易”编译的替代策略或变体?
我应该忘记这个想法作为纯code bloat的一个例子吗?
备注
以下是我为L
的不同值测量的编译时间(以秒为单位)和可执行文件大小(以KB为单位):
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
,例如对于L = 160
,显示511/1253
operator()
的不同版本(使用Clang / GCC)。这些都是针对N < L
的,因此看起来终止专业化N >= L
会被内联。
PS 即可。我会添加标签code-bloat
,但系统不会让我。
答案 0 :(得分:3)
我把这种技术称为魔术开关。
我知道这样做的最有效方法是建立自己的跳转表。
// first, index list boilerplate. Does log-depth creation as well
// needed for >1000 magic switches:
template<unsigned...Is> struct indexes {typedef indexes<Is...> type;};
template<class lhs, class rhs> struct concat_indexes;
template<unsigned...Is, unsigned...Ys> struct concat_indexes<indexes<Is...>, indexes<Ys...>>{
typedef indexes<Is...,Ys...> type;
};
template<class lhs, class rhs>
using ConcatIndexes = typename concat_indexes<lhs, rhs>::type;
template<unsigned min, unsigned count> struct make_indexes:
ConcatIndexes<
typename make_indexes<min, count/2>::type,
typename make_indexes<min+count/2, (count+1)/2>::type
>
{};
template<unsigned min> struct make_indexes<min, 0>:
indexes<>
{};
template<unsigned min> struct make_indexes<min, 1>:
indexes<min>
{};
template<unsigned max, unsigned min=0>
using MakeIndexes = typename make_indexes<min, max-min>::type;
// This class exists simply because [](blah){code}... `...` expansion
// support is lacking in many compilers:
template< typename L, typename R, unsigned I >
struct helper_helper {
static R func( L&& l ) { return std::forward<L>(l)(size<I>()); }
};
// the workhorse. Creates an "manual" jump table, then invokes it:
template<typename L, unsigned... Is>
auto
dispatch_helper(indexes<Is...>, L&& l, unsigned i)
-> decltype( std::declval<L>()(size<0>()) )
{
// R is return type:
typedef decltype( std::declval<L>()(size<0>()) ) R;
// F is the function pointer type for our jump table:
typedef R(*F)(L&&);
// the jump table:
static const F table[] = {
helper_helper<L, R, Is>::func...
};
// invoke at the jump spot:
return table[i]( std::forward<L>(l) );
};
// create an index pack for each index, and invoke the helper to
// create the jump table:
template<unsigned Max, typename L>
auto
dispatch(L&& l, unsigned i)
-> decltype( std::declval<L>()(size<0>()) )
{
return dispatch_helper( MakeIndexes<Max>(), std::forward<L>(l), i );
};
需要一些静态设置,但运行时速度非常快。
i
处于界限内的断言也可能有用。
答案 1 :(得分:1)
如果您的解决方案限制了最大可能值(比如256),您可以使用宏魔术和switch语句来简化它:
#define POS(i) case (i): return F<(i)>(); break;
#define POS_4(i) POS(i + 0) POS(i + 1) POS(i + 2) POS(i + 3)
#define POS_16(i) POS_4(i + 0) POS_4(i + 4) POS_4(i + 8) POS_4(i + 12)
int func(int i)
{
switch(i)
{
POS_16(0)
}
}
另一种可能的解决方案是(使用C ++ 11)使用可变参数模板:
template<int I>
struct FF
{
static int f() { return I; }
};
template<typename... I>
int func(int i)
{
constexpr int (*Func[])() = { I::f... };
return Func[i]();
}
int main(int argc, char** argv)
{
func<FF<0>,FF<1>>(1);
}
答案 2 :(得分:0)
我会在这里采取明显的立场并询问是否&#34;我想通过将其用作模板参数来强调它是constexpr
&#34;值得这个费用,如果:
struct F
{
constexpr size_t operator()(size_t i) const { return i; }
template <size_t I>
constexpr size_t operator()(size <I>) const { return (*this)(I); }
};
不会是一个更简单的解决方案。
答案 3 :(得分:0)
这不完全是一个答案,我的问题仍然存在,但我找到了一个解决方案,在编译方面给人留下了深刻印象。这是问题中给出的解决方案的一个小调整,其中参数R
从operator()
移到外部idx
,终止条件现在包括R
和N
:
template <
typename F, size_t L,
size_t R = 1, size_t N = 0, bool = (R < 2 * L && N < L)
>
struct idx //...
整个代码在新的live example中提供。
这种方法显然减少了R
的大量不必要的专业化组合。编译时间和可执行文件大小急剧下降。例如,我能够使用Clang / GCC为L = 1<<12
(4096)在10.7 / 18.7秒内编译,产生220/239 KB的可执行文件。在这种情况下,nm -C
显示了operator()
的546/250版本。