3

I want to be able avoid calling a function when a condition is false when this is known at compile time. Now I use something like this:

template<bool Enabled>
void fun(params)
{
    //do nothing
}

template<>
void fun<true>(params)
{
    //do something with params.
}

The thing I don't like with this aproach is that params are evaluated even if the function body is empty.

I would like a solution when the function is not called at all and the params won't be evaluated when the condition is false(this might be optimized in the first case with empty function, but I can't assume this is true on every compiler).

Is this even possible?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Mircea Ispas
  • 20,260
  • 32
  • 123
  • 211
  • 1
    Are you sure the compiler isn't doing this already? – Bo Persson Apr 27 '13 at 20:50
  • @BoPersson If the parameters evaluation has side effect they should be evaluated even if the function body is empty. Also there is no stated in the standard AFAIK that empty function should not be called if it's empty(this might be done by most compilers) – Mircea Ispas Apr 27 '13 at 20:56
  • You answered your own question, I believe. – jrok Apr 27 '13 at 20:58
  • @jrok Exactly this is what I want to avoid:) Because I know the side effects are just "getters" that might be invalid on false condition. – Mircea Ispas Apr 27 '13 at 21:01

1 Answers1

13

Yep.

template<bool b, typename Func, typename FuncElse>
struct compile_time_if_functor {
  void operator()( Func&& f, FuncElse&& ) const{
    std::forward<Func>(f)();
  }
};
template<typename Func, typename FuncElse>
struct compile_time_if_functor<false, Func, FuncElse> {
  void operator()( Func&&, FuncElse&& else_f  ) const{
    std:forward<FuncElse>(else_f)();
  }
};
template<bool b, typename Func, typename Else>
void compile_time_if( Func&& f, Else&& elsef ) {
  compile_time_if_functor<b, Func, Else> functor;
  functor(
    std::forward<Func>(f),
    std::forward<Else>(elsef)
  );
}
template<bool b, typename Func>
void compile_time_if( Func&& f ) {
  auto do_nothing = []{};
  compile_time_if<b>( std::forward<Func>(f), std::move(do_nothing) );
}

use:

int main() {
  compile_time_if<expression>([&]{
    // code that runs iff expression is true
  });
  compile_time_if<expression2>([&]{
    // code that runs iff expression2 is true
  },[&]{
    // else clause, runs iff expression2 is false
  });
}

note that the code inside the {} is compiled, but does not run, if it is in the wrong branch of the if. So that code needs to be well formed and legal at the type level, but it doesn't have to be legal to execute at run time. No creating arrays of size 0 in those lambdas!

A fancier method would let you chain if-else blocks indefinitely. I'd use named operator tricks if I wanted to do that.

First, change compile_time_if to return std::integral_constant< bool, b >.

Then, write compile_time_else(Func&&) and compile_time_elseif<bool>(Func&&) which return types that package that Func and override operator*( std::true_type, X ) and operator*( std::false_type, X ) to run or not run Func and return std::true_type or std::false_type.

The end goal would be this syntax:

If<expression>([&]{
  // block 1
})*Else([&]{
  // block 2
});

If<expression>([&]{
  // block 1
})*ElseIf<expression2>([&]{
  // block 2
})*ElseIf<expression3>([&]{
  // block 3
})*Else([&]{
  // block 4
});

allowing full-on cascading flow control. You could even go so far as to do:

compile_time_switch<value>(
  Case<c0, FallThrough>(),
  Case<c1>([&]{
    // block 1
  }),
  Case<c2, Continue>([&]{
    // block 2
  }),
  Case<c3>([&]{
    // block 3
  }),
  Case<c4>([&]->CanContinue{
    // block 4
    if (condition) {
      return Continue;
    } else {
      return Break;
    }
  }),
  Case<c4>([&]{
  }),
  Default([&]{
  })
};

but that is getting ahead of ourselves.

For ideas on how to mess around with duplicating C++ syntax in a way that allows the compiler to manipulate flow control at compile time, look at boost phoenix. I'm just including this here for completeness: actually writing that kind of stuff up is not all that practical, as some poor sod is going to have to maintain it, and the first few times you write this stuff up you are going to do a poor job!

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I think you should explicitly point out that the code in all those lambdas needs to be well-formed, also you kinda mention it with the "is compiled" part. – Xeo Apr 27 '13 at 21:09
  • And now add some macro magic for `If(expression){} Else{};` (works with assignment operator) ;) – dyp Apr 27 '13 at 21:54
  • @DyP Bah, macros are evil. ;) `#define If(expression) compile_time_if() = [&]` and `#define Else * compile_time_else * [&]` and `#define ElseIf(expression) * compile_time_else_if * [&]` with lots of template boilerplate and overload fun behind it. – Yakk - Adam Nevraumont Apr 28 '13 at 02:13