动态调度到模板函数C++



我有一个模板函数(在我的例子中是cuda内核(,其中有少量布尔模板参数可以在运行时之间进行选择。我很乐意在编译时实例化所有排列并动态调度,如下所示(对于布尔值 b0,b1,b2(:

if (b0) {
if (b1) {
if (b2) {
myFunc<true,true,true,otherArgs>(args);
} else {
myFunc<true,true,false,otherArgs>(args);
}
} else {
if(b2) {
myFunc<true,false,true,otherArgs>(args);
} else {
myFunc<true,false,false,otherArgs>(args);
}
}
} else {
if(b1) {
if(b2) {
myFunc<false,true,true,otherArgs>(args);
} else {
myFunc<false,true,false,otherArgs>(args);
}
} else {
if(b2) {
myFunc<false,false,true,otherArgs>(args);
} else {
myFunc<false,false,false,otherArgs>(args);
}
}
}

这写起来很烦人,如果我最终得到 b3 和 b4,情况会呈指数级恶化。

有没有一种简单的方法可以在 C++11/14 中以更简洁的方式重写它,而无需引入大型外部库(如 boost(?像这样:

const auto dispatcher = construct_dispatcher<bool, 3>(myFunc);

dispatcher(b0,b1,b2,otherArgs,args);

没问题。

template<bool b>
using kbool = std::integral_constant<bool, b>;
template<std::size_t max>
struct dispatch_bools {
template<std::size_t N, class F, class...Bools>
void operator()( std::array<bool, N> const& input, F&& continuation, Bools... )
{
if (input[max-1])
dispatch_bools<max-1>{}( input, continuation, kbool<true>{}, Bools{}... );
else
dispatch_bools<max-1>{}( input, continuation, kbool<false>{}, Bools{}... );
}
};
template<>
struct dispatch_bools<0> {
template<std::size_t N, class F, class...Bools>
void operator()( std::array<bool, N> const& input, F&& continuation, Bools... )
{
continuation( Bools{}... );
}
};

活生生的例子。

所以kbool是一个变量,表示编译时常数布尔值。dispatch_bools是一个具有operator()的帮助程序结构。

operator()采用运行时bool数组,从max-1开始生成最大 if/else 分支,每个分支递归为调用dispatch_bools,并计算一个编译时布尔值。

这将生成 2^max 代码;正是您不想编写的代码。

延续一直传递到底部递归(其中max=0(。 此时,所有的编译时布尔值都已建立起来——我们将这些编译时布尔值作为函数参数传入continuation::operator()调用。

希望continuation::operator()是一个可以接受编译时布尔值的模板函数。 如果是,则有 2^max 实例化,每个实例都有 2^max 可能的真/假组合。


要使用它来解决 c++14 中的问题,您只需执行以下操作:

std::array<bool, 3> bargs={{b0, b1, b2}};
dispatch_bools<3>{}(bargs, [&](auto...Bargs){
myFunc<decltype(Bargs)::value...,otherArgs>(args);
});

这很容易,因为 c++14 有autolambda;它可以在 lambda 上operator()模板。 将这些编译时布尔参数转换回模板非类型参数很容易。

请注意,许多名义上的 c++11 编译器都支持 auto-lambda,因为它非常简单。 但是,如果你缺少它,你仍然可以在 c++11 中使用辅助结构体解决这个问题:

template<class OtherArgs>
struct callMyFunc {
Args args;
template<class...Bools>
void operator()(Bools...){
myFunc<Bools::value...,otherArgs>(args);
}
};

现在使用的是:

std::array<bool, 3> bargs={{b0, b1, b2}};
dispatch_bools<3>{}(bargs, callMyFunc<otherArgs>{args});

这基本上是手动编写 c++14 lambda 会做什么。


在 c++14 中,您可以将void替换为auto并返回,而不仅仅是递归,它会为您合理地推断返回类型。

如果您希望在 c++11 中使用该功能,则可以编写大量decltype代码,也可以使用此宏:

#define RETURNS(...) 
noexcept(noexcept(__VA_ARGS__)) 
-> decltype(__VA_ARGS__) 
{ return __VA_ARGS__; }

并写下dispatch_bools的正文,例如:

template<class T, std::size_t N, class F, class...Bools>
auto operator()( std::array<T, N> const& input, F&& continuation, Bools... )
RETURNS(
(input[max-1])?
dispatch_bools<max-1>{}( input, continutation, kbool<true>{}, Bools{}... )
:
dispatch_bools<max-1>{}( input, continutation, kbool<false>{}, Bools{}... )
)

和类似的<0>专业化,并在 C++11 中获得 C++14 样式的回报扣除。

RETURNS使得推断单行函数的返回类型变得微不足道。

有没有简单的方法?不。可以使用乱码模板的混乱来完成吗?当然,为什么不呢。

实现

首先,如果我们有一个类而不是一个函数,这将更容易一些,仅仅是因为参数化的类可以作为模板参数传递。所以我要围绕你的myFunc写一个琐碎的包装器.

template <bool... Acc>
struct MyFuncWrapper {
template <typename T>
void operator()(T&& extra) const {
return myFunc<Acc...>(std::forward<T&&>(extra));
}
};

这只是一个类,MyFuncWrapper<...>()(extra)等同于myFunc<...>(extra)

现在让我们制作我们的调度程序。

template <template <bool...> class Func, typename Args, bool... Acc>
struct Dispatcher {
auto dispatch(Args&& args) const {
return Func<Acc...>()(std::forward<Args&&>(args));
}
template <typename... Bools>
auto dispatch(Args&& args, bool head, Bools... tail) const {
return head ?
Dispatcher<Func, Args, Acc..., true >().dispatch(std::forward<Args&&>(args), tail...) :
Dispatcher<Func, Args, Acc..., false>().dispatch(std::forward<Args&&>(args), tail...);
}
};

呵呵,那里有很多要解释的。Dispatcher类有两个模板参数,然后是一个可变参数列表。前两个参数很简单:我们要调用的函数(作为一个类(和"extra"参数类型。可变参数一开始将为空,我们将在递归期间将其用作累加器(类似于进行尾部调用优化时的累加器(来累积模板布尔列表。

dispatch只是一个递归模板函数。基本情况是当我们没有任何参数时,所以我们只是用我们到目前为止积累的参数调用函数。递归情况涉及一个条件,如果布尔值true,我们累积一个true,如果布尔值false,则累积一个false

我们可以称之为

Dispatcher<MyFuncWrapper, TypeOfExtraArgument>()
.dispatch(extraArgument, true, true, false);

但是,这有点冗长,因此我们可以编写一个宏来使其更平易近人。1

#define DISPATCH(F, A, ...) Dispatcher<F, decltype(A)>().dispatch(A, __VA_ARGS__);

现在我们的电话是

DISPATCH(MyFuncWrapper, extraArgument, true, true, false);

完整的可运行示例

包括myFunc实现的示例。

#include <utility>
#include <iostream>
#define DISPATCH(F, A, ...) Dispatcher<F, decltype(A)>().dispatch(A, __VA_ARGS__);
template <bool a, bool b, bool c, typename T>
void myFunc(T&& extra) {
std::cout << a << " " << b << " " << c << " " << extra << std::endl;
}
template <bool... Acc>
struct MyFuncWrapper {
template <typename T>
void operator()(T&& extra) const {
return myFunc<Acc...>(std::forward<T&&>(extra));
}
};
template <template <bool...> class Func, typename Args, bool... Acc>
struct Dispatcher {
auto dispatch(Args&& args) const {
return Func<Acc...>()(std::forward<Args&&>(args));
}
template <typename... Bools>
auto dispatch(Args&& args, bool head, Bools... tail) const {
return head ?
Dispatcher<Func, Args, Acc..., true >().dispatch(std::forward<Args&&>(args), tail...) :
Dispatcher<Func, Args, Acc..., false>().dispatch(std::forward<Args&&>(args), tail...);
}
};
int main() {
DISPATCH(MyFuncWrapper, 17, true, true, false);
DISPATCH(MyFuncWrapper, 22, true, false, true);
DISPATCH(MyFuncWrapper, -9, false, false, false);
}

结语

上面提供的实现也将允许myFunc返回值,尽管您的示例仅包含返回类型void,所以我不确定您是否需要它。如前所述,对于auto返回类型,实现需要 C++14。如果要在 C++11 下执行此操作,可以将所有返回类型更改为void(无法再从myFunc返回任何内容(,也可以尝试使用decltype将返回类型组合在一起。如果你想在 C++98 中做到这一点,... ... ...祝你好运


1此宏容易受到逗号问题的影响,因此如果您将其传递给零布尔值,则无法正常工作。但是,如果您不打算传递任何布尔值,那么无论如何都不应该经历此过程。

最新更新