Cleanest way to avoid branching with templates

Well there are ways but they require more lines than your current implementation. And I’m not sure if the following implementation improves readability more than just putting your ‘unclean’ section in a separate function.

The following code is probably longer than it will be for you because I implemented two ways (calling directly and getting a function pointer) and two examples to show it really works.

#include <iostream>
#include <string>

template <bool pred0, bool pred1, bool pred2, bool pred3, bool pred4>
void Func1()
{
  std::cout << "func1: " << pred0 << pred1 << pred2 << pred3 << pred4 << std::endl;
}

template <bool pred0, bool pred1, bool pred2, bool pred3, bool pred4>
struct Func1Wrapper
{
  static constexpr auto Ptr = Func1<pred0, pred1, pred2, pred3, pred4>;
  static void Call()
  {
    Func1<pred0, pred1, pred2, pred3, pred4>();
  }
};

template <bool pred0, bool pred1, bool pred2>
void Func2()
{
  std::cout << "func2: " << pred0 << pred1 << pred2 << std::endl;
}

template <bool pred0, bool pred1, bool pred2>
struct Func2Wrapper
{
  static constexpr auto Ptr = Func2<pred0, pred1, pred2>;
  static void Call()
  {
    Func2<pred0, pred1, pred2>();
  }
};

template <template<bool...> class F, bool... preds>
struct Ct
{
  template <bool newPred>
  using Expanded = Ct<F, preds..., newPred>;

  static void CallFunc()
  {
    F<preds...>::Call();
  }

  static auto GetFunc()
  {
    return F<preds...>::Ptr;
  }
};

template <class DeducedPreds, typename BoolLast>
void Rt2Ct(BoolLast predLast)
{
  if (predLast)
    DeducedPreds::template Expanded<true>::CallFunc();
  else
    DeducedPreds::template Expanded<false>::CallFunc();
}

template <class DeducedPreds, typename BoolFirst, typename ...BoolRest>
void Rt2Ct(BoolFirst predFirst, BoolRest... predRest)
{
  if (predFirst)
    Rt2Ct<typename DeducedPreds::template Expanded<true>, BoolRest...>(predRest...);
  else
    Rt2Ct<typename DeducedPreds::template Expanded<false>, BoolRest...>(predRest...);
}

template <template<bool...> class F, typename ...Bools>
void FuncCaller(Bools... preds)
{
  Rt2Ct<Ct<F>, Bools...>(preds...);
}

template <class DeducedPreds, typename BoolLast>
auto Rt2CtGet(BoolLast predLast)
{
  if (predLast)
    return DeducedPreds::template Expanded<true>::GetFunc();
  else
    return DeducedPreds::template Expanded<false>::GetFunc();
}

template <class DeducedPreds, typename BoolFirst, typename ...BoolRest>
auto Rt2CtGet(BoolFirst predFirst, BoolRest... predRest)
{
  if (predFirst)
    return Rt2CtGet<typename DeducedPreds::template Expanded<true>, BoolRest...>(predRest...);
  else
    return Rt2CtGet<typename DeducedPreds::template Expanded<false>, BoolRest...>(predRest...);
}

template <template<bool...> class F, typename ...Bools>
auto FuncGetter(Bools... preds)
{
  return Rt2CtGet<Ct<F>, Bools...>(preds...);
}

int main(int argc, char* argv[])
{
  FuncCaller<Func1Wrapper>(std::stoi(argv[1]), std::stoi(argv[2]), std::stoi(argv[3]), std::stoi(argv[4]), std::stoi(argv[5]));
  auto f2 = FuncGetter<Func2Wrapper>(std::stoi(argv[1]), std::stoi(argv[3]), std::stoi(argv[5]));
  f2();
  return 0;
}

The code requires C++14 (C++11 should be possible with a little rewrite) and is CUDA free because I don’t really see how calling a kernel instead of a normal function changes the problem. Of course you would have to add a few parameters for passing dimGrid and dimBlock and all the args you need.
As it already has been noted you may want to put the if-else stuff outside of the for loop. That is why I also implemented the FuncGetter routine.
Also note that you may want to ensure that the Bools, BoolFirst, etc template arguments really are of type bool.

Compilation, function call and output:

$ g++ -std=c++14 main.cpp && ./a.out 1 0 1 0 0
func1: 10100
func2: 110

CLICK HERE to find out more related problems solutions.

Leave a Comment

Your email address will not be published.

Scroll to Top