Avoiding branching based on the return value of a function that is a template argument

Suppose the following policy classes that take care of one aspect of the algorithm:

struct VoidF { static void f() { ... // some code that has side effects } }; struct BoolF { static bool f() { bool res = ...; // some computation return res; } }; 

The BoolF policy is "improving": when BoolF :: f () returns true , the algorithm may exit. VoidF is a "minor improvement", so it returns void (I don't want to force a user of my library to return bool when that means nothing to him).

Currently, the algorithm is written as follows:

 template <typename F> struct Algorithm { void run() { ... // some computation here if (std::is_same<decltype(F::f()), bool>::value) { if (F::f()) return; } else F::f(); // If F is VoidF, there should be no branching and some // compiler optimizations will be enabled ... // more computation, unless F::f() got rid of it } }; 

Of course, this does not work if the Algorithm is created using VoidF . Is there a way to fix this so that there is no branching in the Algorithm<VoidF>::run() as pointed out by the comment?

+6
source share
3 answers

You should use SFINAE instead of branching at runtime.

The execution of the function should look like this:

 template <typename F> struct Algorithm { void run() { ... // some computation here doRun(); } template<std::enable_if_t<std::is_same<decltype(F::f()), bool>::value, int> = 0> void doRun() { if (F::f()) { // do some more computations if needed or simply remove the if and return } } template<std::enable_if_t<!std::is_same<decltype(F::f()), bool>::value, int> = 0> void doRun() { F::f(); ... // more computation, unless F::f() got rid of it } }; 
+2
source

Here is my own attempt to do this without SFINAE:

 template <typename F> struct Algorithm { void run() { ... // some computation here myRun(std::integral_constant< bool, std::is_same<decltype(F::f()), bool>::value>()); } private: void myRun(std::true_type) { if (F::f()) return; moreComputation(); } void myRun(std::false_type) { F::f(); moreComputation(); } void moreComputation() { ... } }; 
+3
source

Guillaume Racicot answer option:

 template <typename F> struct Algorithm { void run() { ... // some computation here if(doRun()) return; ... // more computation, unless F::f() got rid of it } template<std::enable_if_t<std::is_same<decltype(F::f()), bool>::value, int> = 0> bool doRun() { return F::f(); } template<std::enable_if_t<!std::is_same<decltype(F::f()), bool>::value, int> = 0> bool doRun() { F::f(); return false; } }; 

Type F is known at compile time. Therefore, in the case of VoidF compiler knows that doRun will always return false and therefore must remove all branching code during optimization.

Refresh . Thus, you are not guaranteed the absence of a branch. But that should be pretty easy for the optimizer.

0
source

All Articles