Best way to call template-bool functions

Viewed 275

I have a function like this

template<bool switch1, bool switch2, bool switch3>
void foo(){
}

I defined the function like this because I want the compiler to produce 8 different (hopefully heavily optimized) versions of the function. However, now when calling the function I have to do something like this

inline void call_foo(bool switch1, bool switch2, bool switch3){
    if (switch1 && switch2 && switch3)
        foo<true, true, true>();
    else if (!switch1 && switch2 && switch3)
        foo<false, true, true>();
    // 6 more ifs
}

Is there a more elegant way of doing this?

Note: I know I can skip the template arguments. But profiling the two versions of the code, I found that there is a significant speed improvement with the templated code.

4 Answers

A map would be more elegant, for some definition of elegant:

std::map<std::array<bool,3>, void(*)()> dispatch {
  {{false, false, false}, foo<false, false, false>},
  ///
  {{true, true, true},    foo<true, true, true>}
};

std::array<bool, 3> to{ {switch1, switch2, switch3} };

dispatch[to]();

If you like variadic templates, you can implement it this way:

template<bool ...Args>
struct dispatcher
{
    static void call(){
        foo<Args...>();
    }

    template<class ...Args1>
    static void call(bool b, Args1... ar1){
        if (b)
            dispatcher<Args..., true>::call(ar1...);
        else
            dispatcher<Args..., false>::call(ar1...);
    }

};

void call_foo(bool a, bool b, bool c)
{
    dispatcher<>::call(a,b,c);
}

Similar to map, you may use array:

const std::array<void (*)(), 8> dispatch {
    &foo<false, false, false>, &foo<false, false, true>,
    &foo<false, true, false>, &foo<false, true, true>,
    &foo<true, false, false>, &foo<true, false, true>,
    &foo<true, true, false>, &foo<true, true, true>,
};

dispatch[switch1 << 2 | switch2 << 1 | switch3]();

Jarod42's lookup-table solution is probably the fastest and easiest but, just for completeness sake, a more or less verbatim replacement of the original code might be

template< std::size_t I, std::size_t... Is >
void call_foo( bool switch1, bool switch2, bool switch3, std::index_sequence<I,Is...> )
{
  if( switch1 == bool(I&1) && switch2 == bool(I&2) && switch3 == bool(I&4) )
    foo<bool(I&1),bool(I&2),bool(I&4)>();

  if constexpr( sizeof...(Is) > 0 )
    call_foo( switch1, switch2, switch3, std::index_sequence<Is...>{} );
}

// to be used as

call_foo( true, false, true, std::make_index_sequence<2*2*2>{} );

with an extra index_sequence, this can be also generalized to arbitrary bools count.

A fully generic c++17 version taking a functor may look like:

template< class F, class... T, std::size_t... Js, std::size_t I, std::size_t... Is >
void untemplatize_impl( F&& f, std::index_sequence<Js...> bits, std::index_sequence<I,Is...>, T... bools )
{
  if( I == ( ( unsigned(bools)<<Js ) | ... ) )
    std::forward<F>(f).template operator()<bool(I&(1<<Js))...>();

  if constexpr( sizeof...(Is) > 0 )
    untemplatize_impl( std::forward<F>(f), bits, std::index_sequence<Is...>{}, bools... );
}

template< class F, class... T > // SFINAEd, if needed
void untemplatize( F&& f, T... bools )
{
  untemplatize_impl( std::forward<F>(f), std::make_index_sequence<sizeof...(T)>{}, std::make_index_sequence<(1u<<sizeof...(T))>{}, bools... );
}

// to be used as

untemplatize( foo{}, false, true, true );

the same thing being possible in c++11 as well, but much more messy.

Related