5

我有一个模板函数(在我的例子中是一个 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);
4

2 回答 2

4

没问题。

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()需要一组 runtime bools,并从开始max-1生成 max if/else 分支,每个分支都递归到调用中,dispatch_bools并计算出更多的编译时布尔值。

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

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

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


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

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

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

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

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});

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


中,您可以替换voidauto并返回,而不仅仅是递归,它会为您推断出一个相当好的返回类型。

如果你想要中的那个特性,你可以写很多decltype代码,或者你可以使用这个宏:

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

并写下dispatch_boolslike的正文:

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>专业化,并在样式的返回扣除。

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

于 2018-09-12T03:00:25.833 回答
3

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

执行

首先,如果我们有一个类而不是一个函数,这将更容易一些,因为参数化的类可以作为模板参数传递。所以我要在你的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有两个模板参数,然后是一个可变参数列表。前两个参数很简单:我们想要调用的函数(作为一个类)和“额外”参数类型。可变参数将开始为空,我们将在递归期间将其用作累加器(类似于进行尾调用优化时的累加器)来累积模板布尔列表。

dispatch只是一个递归模板函数。基本情况是当我们没有任何参数时,所以我们只是用我们迄今为止积累的参数调用函数。递归情况涉及一个条件,我们累积 a trueif the Boolean istrue和 a falseif it's 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,所以我不确定您是否需要它。如所写,实现需要 C++14 作为auto返回类型。如果您想在 C++11 下执行此操作,您可以将所有返回类型更改为void(不能再返回任何内容myFunc),或者您可以尝试将返回类型与decltype. 如果你想在 C++98 中做到这一点,………………祝你好运


1此宏容易受到逗号问题的影响,因此如果您将零布尔值传递给它,它将无法工作。但是如果你不打算传递任何布尔值,你可能无论如何都不应该经历这个过程。

于 2018-09-12T02:29:44.613 回答