Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected template behavior with int parameter: conditional expressions ignored?

Tags:

c++

templates

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?

like image 839
J.Kraftcheck Avatar asked May 29 '14 18:05

J.Kraftcheck


Video Answer


2 Answers

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).

like image 179
CygnusX1 Avatar answered Sep 29 '22 02:09

CygnusX1


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); }
like image 37
Jarod42 Avatar answered Sep 29 '22 01:09

Jarod42