Suppose I have a versatile function with about four boolean flags:
int do_something(int arg, bool flag1, bool flag2, bool flag3, bool flag4) {
for(int i = 0; i < 1000000; i++) {
if(flag1)
// Do something 1
if(flag2)
// Do something 2
if(flag3)
// Do something 3
if(flag4)
// Do something 4
//Do something else 5
}
}
But I don't want to incur any costs for branching on these flags in the inner loop so I change them to templates (allowing the compiler to optimize away the conditionals):
template<bool flag1, bool flag2, bool flag3, bool flag4>
int do_something_helper(int arg) {
for(int i = 0; i < 1000000; i++) {
if(flag1)
// Do something 1
if(flag2)
// Do something 2
if(flag3)
// Do something 3
if(flag4)
// Do something 4
//Do something else 5
}
}
How can I write the do_something method now? The only way I know is as follows:
int do_something(int arg, bool flag1, bool flag2, bool flag3, bool flag4) {
if(flag1) {
if(flag2) {
if(flag3) {
if(flag4) {
return do_something_helper<true,true,true,true>(arg);
}else{
return do_something_helper<true,true,true,false>(arg);
}
}else{
if(flag4) {
return do_something_helper<true,true,false,true>(arg);
}else{
return do_something_helper<true,true,false,false>(arg);
}
}
//... You get the picture
}
Is there some way to get the compiler to write the above code automatically so I don't have to include this ugly monstrosity in my beautiful code-base?
What I would do is take a functor and a pack of arguments and an argument index and a range. Then I would replace the indexed argument with std::integral_constant<type, value>
and call the functor. The bool
case is easiest, as the range is obvious, so I would write that one first.
Then you can chain such replaces and functors to replace each one of the bool
s with compile time types. I would use the same functor for them all, with N overloads, esch replacing one bool
with std::integral_constant<bool, X>
where X
is a template
parameter.
The last one would then call the final method, with integral_constant
instead of bool
.
Note that this expands to an exponential amount of instantiations, so be careful.
The argument manipulation code would be fun to write.
Here is a live example .
Amusingly, the boilerplate to do the above is probably still bulkier, but hopefully less typo-prone and easier to test.
#include <iostream>
#include <tuple>
template<unsigned...Is> struct indexes {typedef indexes<Is...> type;};
template<unsigned min, unsigned max, unsigned...Is> struct make_indexes: make_indexes<min, max-1, max-1, Is...> {};
template<unsigned min, unsigned...Is> struct make_indexes<min, min, Is...>: indexes<Is...> {};
template<unsigned max, unsigned min=0>
using Indexes = typename make_indexes<min, max>::type;
template<unsigned index, typename Functor, typename... Args, unsigned... Before, unsigned... After>
void map_bool_to_compile_time_helper( indexes<Before...>, indexes<After...>, Functor&& f, std::tuple<Args...> args )
{
if (std::get<index>( args )) {
std::forward<Functor>(f)( std::get<Before>(args)..., std::true_type(), std::get<After>(args)... );
} else {
std::forward<Functor>(f)( std::get<Before>(args)..., std::false_type(), std::get<After>(args)... );
}
}
template<unsigned index, typename Functor, typename... Args>
void map_bool_to_compile_time( Functor&& f, Args&&... args )
{
map_bool_to_compile_time_helper<index>( Indexes<index>(), Indexes<sizeof...(Args), index+1>(), std::forward<Functor>(f), std::make_tuple<Args&&...>(std::forward<Args>(args)...) );
}
template<typename Functor, unsigned... indexes>
struct map_bools_to_compile_time_helper;
template<typename Functor, unsigned index, unsigned... indexes>
struct map_bools_to_compile_time_helper<Functor, index, indexes...> {
Functor&& f;
map_bools_to_compile_time_helper(Functor&& in):f(std::forward<Functor>(in)) {}
template< typename... Args>
void operator()( Args&&... args) const {
map_bool_to_compile_time<index>( map_bools_to_compile_time_helper<Functor, indexes...>{std::forward<Functor>(f)}, std::forward<Args>(args)... );
}
};
template<typename Functor>
struct map_bools_to_compile_time_helper<Functor> {
Functor&& f;
map_bools_to_compile_time_helper(Functor&& in):f(std::forward<Functor>(in)) {}
template<typename... Args>
void operator()( Args&&... args) const {
std::forward<Functor>(f)(std::forward<Args>(args)...);
}
};
template<unsigned... Is, typename Functor, typename... Args>
void map_bools_to_compile_time( indexes<Is...>, Functor&& f, Args&&... args ) {
map_bools_to_compile_time_helper<Functor, Is...>{ std::forward<Functor>(f) }( std::forward<Args>(args)... );
}
struct test {
template<bool b>
void operator()( int x, std::integral_constant< bool, b > ) { std::cout << x << ": " << b <<"!\n"; }
};
struct test2 {
template<bool b0, bool b1, bool b2>
void operator()( int x, std::integral_constant< bool, b0 >, std::integral_constant< bool, b1 >, std::integral_constant< bool, b2 > )
{
std::cout << x << ": " << b0 << b1 << b2 << "\n";
}
};
int main() {
map_bools_to_compile_time( indexes<1>(), test(), 1, true );
map_bool_to_compile_time<1>( test(), 2, false );
map_bools_to_compile_time( indexes<1,2,3>(), test2(), 3, true, false, true );
}
Updated with support for any number of arguments at any number of indexes.
You can use templates to organize static dispatch - which will allow to replace branching statement with function overload. This is a rather simple idea, here is a small example:
template <int Val>
struct Int2Type
{
static const int val_= Val;
};
int do_something(int arg, Int2Type<1>)
{
// do smth when flag == 1
}
int do_something(int arg, Int2Type<2>)
{
// do smth when flag == 2
}
... the same principle is applied (by the value of a flag needed overloaded function is called)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.