The following code does not work as expected (or at least as I expected). All versions of g++ that I tried fail at a template recursion limit. The output seems to indicate that the conditional statements are ignored and the final else block is used regardless of the value of P.
template <int P> inline REAL const_pow ( REAL value );
template < > inline REAL const_pow<0>( REAL value ) { return 1.0; }
template < > inline REAL const_pow<1>( REAL value ) { return value; }
template < > inline REAL const_pow<2>( REAL value ) { return value*value; }
template <int P> inline REAL const_pow ( REAL value )
{
if (P < 0)
return const_pow<-P>( 1.0/value );
else if (P % 2 == 0)
return const_pow<2>( const_pow<P/2>(value) );
else
return value * const_pow<P-1>( value );
}
The problem doesn't seem to be with negative values (exclusively.) If I re-order the conditional such that the negative-value case is last, the last block is still taken every time.
I have a working solution using a helper class and more complex specialization. But this version was a lot more readable and should achieve the same effect (with optimization enabled). Why doesn't it work?
Keep in mind that all branches need to be compiled (evaluated at template-evaluation time) before the actual execution even begins! For that reason, const_pow<3>
will try to instantiate const_pow<-3>
even if it is ultimately never run. Which in return requires const_pow<3>
again...
What you need is to disable template evaluation of incorrect branch alltogether. This can be resolved either through manually crafted type-traits, or through C++11 std::enable_if
.
Try the following:
#include <iostream>
typedef float REAL;
template <int P> inline REAL const_pow ( REAL value );
template < > inline REAL const_pow<0>( REAL value ) { return 1.0; }
template < > inline REAL const_pow<1>( REAL value ) { return value; }
template < > inline REAL const_pow<2>( REAL value ) { return value*value; }
template <int P, bool negative>
struct const_pow_helper { //instantiate this when P is positive
static inline REAL call(REAL value) {
return const_pow<2>(const_pow<P / 2>(value)) * const_pow<P % 2>(value);
}
};
template <int P>
struct const_pow_helper<P, true> { //instantiate this when P is negative
static inline REAL call(REAL value) {
return const_pow_helper<-P, false>::call(1.0/value);
}
};
template <int P> inline REAL const_pow ( REAL value )
{
return const_pow_helper<P, P<0 >::call(value);
}
int main() {
std::cout << const_pow<10>(2.0f) << std::endl;
std::cout << const_pow<-10>(2.0f) << std::endl;
};
Notice that the negative version of the const_pow_helper
will be instantiated only for negative P
. This decision is handled by the template evaluator and not an ordinary if
.
The if
for the positive P
has been avoided as well, by using integer division (P/2)
and multiplying with the remainder value if it exists (P%2)
.
Solution with std::enable_if
: (https://ideone.com/2lMjv1)
template <int P>
constexpr typename std::enable_if<P == 0, REAL>::type
const_pow(REAL ) { return 1.0; }
template <int P>
constexpr typename std::enable_if<P == 1, REAL>::type
const_pow(REAL value) { return value; }
template <int P>
constexpr typename std::enable_if<P == 2, REAL>::type
const_pow( REAL value ) { return value * value; }
template <int P>
constexpr typename std::enable_if<2 < P, REAL>::type
const_pow(REAL value)
{
return const_pow<2>(const_pow<P / 2>(value)) * const_pow<P % 2>(value);
}
template <int P>
constexpr typename std::enable_if<P < 0, REAL>::type
const_pow(REAL value) { return const_pow<-P>(1.0 / value); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With