2014-04-23 58 views
6

我想了解一些我递过来的递归C++模板代码,并且我遇到了一些奇怪的行为。出于某种原因,编译器似乎能够在编译时添加两个值,但是必须在运行时留下左移。即使如此,只有在尝试使用C++ 11进行构建时才会出现问题。使用C++ 11时,C++递归模板的奇怪行为

的代码(我已经煮下来,以后你会看到)定义2对模板 - 一对名为shftshft_aux和一个名为addadd_aux产生自己递归对。顺便说一句,add模板不应该是有用的,它的唯一目的是证明问题,而不是生成一个实际的min值。

如果我编译这个没有命令行参数的代码,它编译得很好。但是,如果我指定-std=c++11 -stdlib=libc++,add_aux上的static_assert仍然正常,但shft_aux上的static_assert现在会生成一个编译时错误,说static_assert expression is not an integral constant expression

为什么左移与加法不同?

谢谢, 克里斯

附:我使用铛++版本Apple LLVM version 5.1 (clang-503.0.38) (based on LLVM 3.4svn)

#include <climits> 

template <unsigned size> struct shft; // forward 

template <unsigned size> 
struct shft_aux 
{ 
    static const int min = shft<size>::min; 
}; 

template <unsigned size> 
struct shft 
{ 
    typedef shft_aux<size - 1> prev; 
    static const int min = prev::min << CHAR_BIT; 
}; 

// Base specialization of shft, puts an end to the recursion. 
template <> 
struct shft<1> 
{ 
    static const int min = SCHAR_MIN; 
}; 


// ----- 

template <unsigned size> struct add; // forward 

template <unsigned size> 
struct add_aux 
{ 
    static const int min = add<size>::min; 
}; 

template <unsigned size> 
struct add 
{ 
    typedef add_aux<size - 1> prev; 
    static const int min = prev::min + CHAR_BIT; 
}; 

// Base specialization of add, puts an end to the recursion. 
template <> 
struct add<1> 
{ 
    static const int min = SCHAR_MIN; 
}; 


// ----- 

int main() 
{ 
    static_assert(shft_aux<sizeof(int)>::min < 0, "min is not negative"); 
    static_assert(add_aux<sizeof(int)>::min < 0, "min is not negative"); 

    return 0; 
} 
+0

这就像那个老笑话......“医生,当我这样做的时候会很痛苦......”“所以不要这样做!”既然我完全理解了这个问题,并有了标准的支持,我就有信心提出一种解决问题的不同方式,而不需要左移负数。 –

回答

7

C++ 11标准,[expr.shift]/2

E1 << E2值是E1左移E2位位置;空位被零填充。如果E1有一个无符号类型,[...]。否则,如果E1具有带符号类型和非负值,并且 E1 * 2 E2在结果类型中可表示为 ,则这是结果值; 否则,行为是未定义的

[重点矿山]

这是通过DR1457这使得换档进入“符号位”定义的行为受到轻微的影响:

否则,如果E1具有签名的类型和非负值,并且 E1 * 2 E2可以在结果类型[...]的相应的无符号类型中表示。

无论如何,在OP中,E1是负数,所以仍然是未定义的行为。因此,它是不允许的内部常量表达式

[expr.const]/2 甲条件表达式芯常量表达式除非它涉及以下作为一个潜在的一个评估子表达式[...]

  • [...]
  • 未数学上定义的或不在其类型表示的值的范围内的结果;

即点已改变(DR1313);在n3485它说:

  • 将有不确定的操作[注操作:,包括例如,带符号的整数过 流量(第5章),某些指针运算(5.7),除以零点(5.6)或某些移位操作(5.8) - 结束注释];

[class.static.data]/3

如果非易失性const static数据成员是整型或枚举类型的,其在类定义声明可以指定一个括号或等号初始值设定项其中初始值子句这是一个赋值表达式是一个常量表达式


结论:移位SCHAR_MIN不是一个常量表达式,这样就可以这样做,在一个静态数据成员的同类初始化。

提示:总是用-Wall -Wextra -pedantic进行编译。不使用参数IMO对于g ++和兼容的编译器来说是个坏主意。 g ++/clang ++默认使用gnu99模式(see clang doc),它是C++ 98 AFAIK的扩展。此外,你会错过许多重要的警告。

+1

不知何故,我得到的印象是引用标准本身不是很有用。也许我应该坚持我的座右铭“n3485是C++ 11的最佳草案”。 – dyp

+0

非常感谢您引用标准!你是否因为这样做而得到一些负面反馈? –