使用int参数的意外模板行为:条件表达式被忽略?

时间:2014-05-29 18:47:20

标签: c++ templates

以下代码无法按预期工作(或至少符合我的预期)。我尝试的所有g ++版本都在模板递归限制时失败。输出似乎表明忽略了条件语句,并且无论P的值如何都使用最后的else块。

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 );
}

问题似乎不是负值(仅限于此。)如果我重新排序条件使得负值情况是最后一个,那么每次仍然会使用最后一个块。

我有一个使用辅助类和更复杂的专业化的工作解决方案。但是这个版本更具可读性并且应该达到相同的效果(启用优化)。为什么不起作用?

3 个答案:

答案 0 :(得分:5)

请记住,在实际执行开始之前,需要编译所有分支(在模板评估时评估)!出于这个原因,const_pow<3>将尝试实例化const_pow<-3>,即使它最终永远不会运行。反过来又需要const_pow<3> ......

您需要的是完全禁用错误分支的模板评估。这可以通过手工制作的类型特征或通过C ++ 11 std::enable_if来解决。

尝试以下方法:

#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;
};

请注意,const_pow_helper的否定版本仅针对否定P进行实例化。此决定由模板评估程序处理,而不是普通的if

通过使用整数除法if并将剩余值乘以P,也可以避免使用(P/2)(P%2)

答案 1 :(得分:1)

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); }

答案 2 :(得分:0)

让我列举你的通用函数:

template<int P> inline REAL const_pow ( REAL value );  // (1)
template<> inline REAL const_pow<0>( REAL value );     // (2)
template<> inline REAL const_pow<1>( REAL value );     // (3)
template<> inline REAL const_pow<2>( REAL value );     // (4)

如果您编写样本/简单程序,如:

int main()
{
   cout << const_pow<2>(5) << endl;
}

以下通用函数在编译时实例化:

 REAL const_pow<2>; // (4)

而且没有了。所以,你的程序只有第四个专门化而没有更多代码,而main将直接执行第二个特化而不使用(1),因为(4)是目前唯一存在的。(/ p>

如果你写:

 cout << const_pow<3>(5) << endl;

只有以下专业化是即时的:

 REAL const_pow<3>; // (1)

和(1)将再次实例化,因为它的定义:

REAL const_pow<-3> // (1), from your if statement
REAL const_pow<2>  // (2), from your if else statement
REAL const_pow<2>  // (2), already instantiated (last else statement).

const_pow<-3>将实例化(再次,(1)的主体):

REAL const_pow<3> // (1), already instantiated (original function call)
REAL const_pow<2> // (2), already instantiated (just above).
REAL const_pow<-4> // (1) and so on.

一次又一次,编译器将实例化(从最后的else语句),&lt; -4&gt;,&lt; 4&gt;,&lt; -5&gt;,&lt; 5&gt;,&lt; -6&gt;,&lt; 6个递归。

请注意,专门化只会检查当前现有代码的语法。因此,如果选择版本(1),则会显示其正文中出现的每个模板表达式。在使用模板时,您应该始终以这种方式思考。

C ++标准委员会的一些成员提出了static if语句,以便只允许部分代码的编译时实例化。