启用 c++11 时 c++ 递归模板的奇怪行为

Strange behavior with c++ recursive templates when c++11 is enabled

本文关键字:c++11 c++ 递归 启用      更新时间:2023-10-16

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

代码(我已经归结了,您将在后面看到(定义了两对模板 - 一对名为 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

为什么左移与加法的处理方式不同?

谢谢克里斯

附言我正在使用 clang++ 版本 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;
}

C++11 Standard, [expr.shift]/2

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

[强调我的]

这受到DR1457的轻微影响,这使得转换为"符号位">定义的行为:

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

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

[expr.const]/2 条件表达式是核心常量表达式,除非它涉及以下之一作为潜在评估的子表达式 [...]

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

这一点已经改变(DR1313(;在n3485中它说:

  • 具有未定义行为的操作 [ 注意:例如,包括有符号整数 over- 流(条款 5(、某些指针算术 (5.7(、除以零 (5.6( 或某些移位运算 (5.8( — 尾注 ];

[class.static.data]/3

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


结论:移位SCHAR_MIN不是一个常量表达式,因此不能在静态数据成员的类内初始值设定项中执行此操作。

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