我正在尝试使用编译时生成的数组实现快速函数分派器,以便能够在运行时在O(1)中使用它。
一些代码行只是为了澄清:
template<int i>
void f()
{
// do stuff
}
// specialized for every managed integer
template<>
void f<1>
{
// do stuff
}
Dispatcher<1,5,100,300> dispatcher;
dispatcher.execute(5); // this should call f<5>()
让我们将调度程序的输入数量N(在这种情况下为4)和M在调度程序的最大输入值(300)中作为呼叫。
我已经能够创建大小等于M的数组。这利用了这样的事实:在运行时您可以执行以下操作:
dispatcher.execute(5) -> internalArray[5]();
这当然可以,但是对于大尺寸的数组不可行。
最好的办法是只生成一个由N个元素组成的数组,并做一些数学技巧将输入索引转换为第二个数组的索引。
在此示例中,某些东西分别将1,5,100,300转换为0,1,2,3。我已经能够做一种预处理方法来转换它们,但是我正在寻找一种避免此步骤的方法。
换句话说,我认为我正在寻找某种最小的完美哈希,这些哈希可以在编译时针对我的特定情况以非常有效的方式使用(理想情况下没有任何开销,例如:goto:MyInstruction)。 / p>
我不是在寻找使用虚函数,std :: map或复杂操作的替代方法。
请询问是否有不清楚的地方。
PS我正在使用C ++ 11,但是欢迎任何想法
[编辑]我知道标签是GCC的值语言扩展。有了这些,我也许可以实现我的目标,但需要一个可移植的解决方案。
答案 0 :(得分:7)
好吧,我不知道您是否能够做自己想做的事。在我看来,编写一个为任何输入创建完美哈希函数的代码对我来说似乎很……无法实现。
无论如何,这是编写代码的简单解决方案。它是C ++ 17,但有些麻烦就可以使其与C ++ 11一起使用。
template<int i> void f();
template <int... Is>
struct Dispatcher
{
template <int I> constexpr auto execute_if(int i)
{
if (I == i)
f<I>();
}
constexpr auto execute(int i)
{
(execute_if<Is>(i), ...);
}
};
auto test()
{
Dispatcher<1,5,100,300> dispatcher;
dispatcher.execute(5);
}
上面的代码转换为一个简单的跳转,因为5
是一个编译时间常数:
test(): # @test()
jmp void f<5>() # TAILCALL
如果参数是运行时变量,那么它将进行一系列比较:
auto test(int i)
{
Dispatcher<1,5,100,300> dispatcher;
dispatcher.execute(i);
}
test(int): # @test(int)
cmp edi, 99
jg .LBB0_4
cmp edi, 1
je .LBB0_7
cmp edi, 5
jne .LBB0_9
jmp void f<5>() # TAILCALL
.LBB0_4:
cmp edi, 100
je .LBB0_8
cmp edi, 300
jne .LBB0_9
jmp void f<300>() # TAILCALL
.LBB0_9:
ret
.LBB0_7:
jmp void f<1>() # TAILCALL
.LBB0_8:
jmp void f<100>() # TAILCALL
可以对解决方案进行改进以执行二进制搜索,但这并不简单。
答案 1 :(得分:4)
基于@bolov的答案,当i
不是常数时,可以通过以下方式使用任意调度算法:
constexpr auto execute(int i)
{
(execute_if<Is>(i), ...);
}
收件人:
constexpr auto execute(unsigned i)
{
(execute_if<Is>(i), ...);
}
然后添加:
constexpr auto execute (int& i)
{
// Add arbitrary dispatch mechanism here
}
完整的示例,与C ++ 11兼容,并且在std::map
不是常数时使用了笨拙的i
(最坏情况下的复杂度日志n)(我放弃了constexpr
的东西在C ++ 11中生活轻松):
#include <map>
#include <iostream>
std::map <int, void (*) ()> map;
template <int i> void f ();
template <> void f <1> () { std::cout << "f1\n"; }
template <> void f <2> () { std::cout << "f2\n"; }
template <> void f <3> () { std::cout << "f3\n"; }
template <> void f <4> () { std::cout << "f4\n"; }
template <> void f <5> () { std::cout << "f5\n"; }
template <int ... Is>
struct Dispatcher
{
template <int first> void execute_if (int i)
{
if (first == i)
{
std::cout << "Execute f" << i << " via template\n";
f <first> ();
}
}
template <int first, int second, int... rest> void execute_if (int i)
{
if (first == i)
{
std::cout << "Execute f" << i << " via template\n";
f <first> ();
}
else
execute_if <second, rest...> (i);
}
void execute (unsigned i)
{
execute_if <Is...> (i);
}
void execute (int& i)
{
std::cout << "Execute f" << i << " via map\n";
map.at (i) ();
}
};
int main()
{
map [1] = f <1>;
map [2] = f <2>;
map [3] = f <3>;
map [4] = f <4>;
map [5] = f <5>;
Dispatcher <1, 2, 4> dispatcher;
dispatcher.execute (2);
int i = 4;
dispatcher.execute (i);
}
输出:
Execute f2 via template
f2
Execute f4 via map
f4
编辑:根据OP的要求,以下是使用二进制搜索而不是std::map
的版本。这样做的关键是在Dispatcher
构造函数中构建要搜索的数组。
#include <vector>
#include <iostream>
template <int i> void f ();
template <> void f <1> () { std::cout << "f1\n"; }
template <> void f <2> () { std::cout << "f2\n"; }
template <> void f <3> () { std::cout << "f3\n"; }
template <> void f <4> () { std::cout << "f4\n"; }
template <> void f <5> () { std::cout << "f5\n"; }
using ve = std::pair <int, void (*) ()>;
template <int ... Is>
struct Dispatcher
{
template <int first> void execute_if (int i)
{
if (first == i)
{
std::cout << "Execute f" << i << " via template\n";
f <first> ();
}
}
template <int first, int second, int... rest> void execute_if (int i)
{
if (first == i)
{
std::cout << "Execute f" << i << " via template\n";
f <first> ();
}
else
execute_if <second, rest...> (i);
}
void execute (unsigned i)
{
execute_if <Is...> (i);
}
void execute (int& i)
{
std::cout << "Execute f" << i << " via binary search\n";
auto lb = lower_bound (indexes.begin (), indexes.end (), ve (i, nullptr),
[] (ve p1, ve p2) { return p1.first < p2.first; });
if (lb != indexes.end () && lb->first == i)
lb->second ();
}
template <int first> void append_index ()
{
indexes.emplace_back (ve (first, f <first>));
}
template <int first, int second, int... rest> void append_index ()
{
append_index <first> ();
append_index <second, rest...> ();
}
Dispatcher ()
{
append_index <Is...> ();
}
private:
std::vector <ve> indexes;
};
int main()
{
Dispatcher <1, 2, 4> dispatcher;
dispatcher.execute (2);
int i = 4;
dispatcher.execute (i);
}
答案 2 :(得分:2)
OP要求按照Bolov的解决方案示例使用C ++ 11解决方案,以保持constexpres
-ness。
嗯...这不是个好主意,因为C ++ 11中的constexpr
函数/成员需要递归并返回一个值。并且编译器对模板递归设置了严格的限制,如果N
(sizeof...(Is)
)很高,这可能是个问题。
无论如何...我能想象的最好的是
template <int... Is>
struct Dispatcher
{
template <typename = void>
constexpr int execute_h (int) const
{ /* wrong case; exception? */ return -1; }
template <int J0, int ... Js>
constexpr int execute_h (int i) const
{ return J0 == i ? (f<J0>(), 0) : execute_h<Js...>(i); }
constexpr int execute (int i) const
{ return execute_h<Is...>(i); }
};
可以使用,计算f<>()
的编译时间,如下所示
void test()
{
constexpr Dispatcher<1,5,100,300> dispatcher;
constexpr auto val1 = dispatcher.execute(5);
constexpr auto val2 = dispatcher.execute(6);
std::cout << val1 << std::endl; // print 0 (5 is in the list)
std::cout << val2 << std::endl; // print -1 (6 isn't in the list)
}
f<>()
也必须为constexpr
,在C ++ 11中,不能返回void
;我已经使用了以下
template <int i>
constexpr int f ()
{ return i; }
答案 3 :(得分:2)
bolov的解决方案有一点改进(IMHO)
在执行execute_if
时写true
返回f<I>()
,否则返回false
template <int I>
constexpr auto execute_if (int i) const
{ return I == i ? f<I>(), true : false; }
而不是使用逗号运算符进行模板折叠
template <int ... Is>
constexpr auto execute(int i) const
{ (execute_if<Is>(i), ...); }
我们可以使用or(||
)运算符
template <int ... Is>
constexpr auto execute(int i) const
{ (execute_if<Is>(i) || ...); }
使用逗号运算符,execute_is<Is>(i)
永远被调用Is
,当第一个Is
等于i
时也是如此;使用||
会造成短路,即execute_is<Is>(i)
仅在得到等于Is
的{{1}}之前被调用。
答案 4 :(得分:0)
从理论上讲(!),您可以使用C ++模板创建完美的哈希函数。
但是我认为这样做很难。