【问题标题】:Dynamic Dispatch to Template Function C++动态分派到模板函数 C++
【发布时间】:2018-09-12 01:26:17
【问题描述】:

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

【问题讨论】:

  • func_array[4*b0+2*b1+b2](args),甚至func_array[b0][b1][b2](args)

标签: c++ c++11 templates


【解决方案1】:

没问题。

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

Live example.

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

这个operator() 采用一组运行时bools,并从max-1 开始生成最大的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);
});

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

请注意,许多名义上 的编译器都支持自动 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 会做什么。


中,您可以将void 替换为auto 并返回而不是仅仅递归,它会为您很好地推断出返回类型。

如果你想要 中的那个功能,你可以写很多 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{}... )
)

&lt;0&gt; 特化类似,并在 中获得 样式的返回扣除。

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

【讨论】:

  • 您的格式已关闭,您对布尔常量的使用略有不一致(Bools{}.../Bools::value...decltype(Bargs)::value... 可以写为Bargs...。还是我笨,this 用于 C++11 的转换?
  • @PasserBy constexpr 到 bool 的转换是 C++14 的积分常数功能。而且我从来没有把Bools::value...放在()中,只放在&lt;&gt;中。
  • It's not。你的意思是operator()
  • @PasserBy 嗯。也许这是旧 C++11 编译器中的错误;我记得当时在编译器更新之前它不起作用。
  • 这太好了,谢谢!在 VS2015 中从未引用的形式参数到 (以及 1 和 0,以及基本案例专业化中的输入)执行此操作时,我收到了一些长警告。您是否建议仅取消调度代码周围的警告?
【解决方案2】:

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

实施

首先,如果我们有一个类而不是一个函数,这会更容易一些,因为参数化的类可以作为模板参数传递。所以我要在你的myFunc 周围写一个简单的包装器。

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

这只是一个MyFuncWrapper&lt;...&gt;()(extra) 等价于myFunc&lt;...&gt;(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 类有两个模板参数和一个可变参数列表。前两个参数很简单:我们想要调用的函数(作为一个类)和“额外”参数类型。可变参数开始时为空,我们将在递归过程中将其用作累加器(类似于tail call optimization 时的累加器)来累加模板布尔列表。

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 的返回类型,所以我不确定您是否需要它。如所写,实现需要 C++14 来处理 auto 返回类型。如果您想在 C++11 下执行此操作,您可以将所有返回类型更改为 void(不能再从 myFunc 返回任何内容),或者您可以尝试将返回类型与 decltype 组合在一起.如果你想在 C++98 中做到这一点,... ... ... ... 祝你好运


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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-16
    相关资源
    最近更新 更多