【问题标题】:Strange behavior with c++ recursive templates when c++11 is enabled启用 c++11 时 c++ 递归模板的奇怪行为
【发布时间】:2014-04-23 16:51:15
【问题描述】:

我正在尝试理解一些递给我的递归 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++ templates c++11 recursion clang++


【解决方案1】:

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

E1 &lt;&lt; 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数据成员是整数或枚举类型,它在类定义中的声明可以指定一个brace-or-equal-initializer,其中每个initializer-clause 是一个赋值表达式是一个常量表达式


结论:移位SCHAR_MIN 不是常量表达式,因此您不能在静态数据成员的类内初始化程序中这样做。

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

【讨论】:

  • 不知何故,我觉得引用标准本身并不是很有用。也许我应该坚持我的座右铭“n3485 是 C++11 的最佳草案”。
  • 非常感谢您引用了标准!你这样做有没有得到一些负面反馈?
  • @BettyCrokker 我以前只引用 n3485;将 n3485 和国际标准混合使用可能会造成混淆。这些差异通常只对语言律师很重要,因为大多数编译器维护者最终都会实施建议的缺陷解决方案。此外,这些差异对您的问题并不重要,因此它们在这里可能只是噪音。
  • @dyp 我记得你在N3485 上发表的评论,我想知道你是否有任何要添加到this comment thread on N3485 的内容。
猜你喜欢
  • 2018-08-04
  • 2014-01-31
  • 1970-01-01
  • 2017-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-30
  • 2015-11-22
相关资源
最近更新 更多