【问题标题】:Confusion about constant expressions关于常量表达式的困惑
【发布时间】:2013-04-17 10:29:40
【问题描述】:

这是this topic 的某种后续行动,涉及其中的一小部分。与上一个主题一样,让我们​​考虑一下我们的编译器具有用于std::initializer_liststd::arrayconstexpr 函数。现在,让我们直奔主题。

This works:

#include <array>
#include <initializer_list>

int main()
{
    constexpr std::array<int, 3> a = {{ 1, 2, 3 }};
    constexpr int a0 = a[0];
    constexpr int a1 = a[1];
    constexpr int a2 = a[2];
    constexpr std::initializer_list<int> b = { a0, a1, a2 };

    return 0;
}

This does not:

#include <array>
#include <initializer_list>

int main()
{
    constexpr std::array<int, 3> a = {{ 1, 2, 3 }};
    constexpr std::initializer_list<int> b = { a[0], a[1], a[2] };

    return 0;
}

它因这个错误而崩溃:

error: 'const std::initializer_list<int>{((const int*)(&<anonymous>)), 3u}' is not a constant expression

尽管我同时阅读了一些关于constexpr 和常量表达式的论文,但这种行为对我来说仍然没有任何意义。为什么第一个示例被认为是有效的常量表达式而不是第二个?我欢迎任何解释,以便我之后可以安息。

注意:我会马上准确地说,Clang 将无法编译第一个 sn-p,因为它没有实现为 C++14 计划的 constexpr 库添加.我使用的是 GCC 4.7。

编辑:好的,这里有一个重要的例子来说明什么被拒绝,什么不是:

#include <array>
#include <initializer_list>

constexpr int foo = 42;
constexpr int bar() { return foo; }
struct eggs { int a, b; };

int main()
{
    constexpr std::array<int, 3> a = {{ 1, 2, 3 }};
    constexpr int a0 = a[0];
    constexpr int a1 = a[1];
    constexpr int a2 = a[2];

    // From Xeo and Andy tests
    constexpr std::array<int, 1> a = { bar() }; // OK
    constexpr std::array<int, 3> b = {{ a[0], a[1], a[2] }}; // OK
    std::initializer_list<int> b = { a[0], a[1], a[2] }; // OK
    constexpr std::initializer_list<int> b = { a0, a1, a2 }; // OK
    constexpr std::initializer_list<int> b = { foo }; // OK
    constexpr std::initializer_list<int> c = { bar() }; // ERROR
    constexpr std::initializer_list<int> b = { a[0], a[1], a[2] }; // ERROR

    // From Matheus Izvekov and Daniel Krügler
    constexpr eggs good = { 1, 2 }; // OK
    constexpr std::initializer_list<eggs> bad = { { 1, 2 }, { 3, 4 } }; // ERROR
    constexpr std::initializer_list<eggs> bad2 = { good, good }; // ERROR

    return 0;
}

【问题讨论】:

  • “GCC 有错误”怎么样? :) (不是说它有一个,只是一种可能性。)实际上,您应该能够通过编写自己的类似物来测试没有constexpr 添加的内容。另外,constexpr std::array&lt;int, 3&gt; b = {{ a[0], a[1], a[2] }}; 呢?
  • 也许this 有助于缩小问题范围
  • @Xeo 无论我对数组做什么似乎都可以正常工作(包括您的示例,以及只有std::arrays 而不是std::initializer_list 的Andy 示例)。似乎问题只发生在编译时std::initializer_list。没有constexprstd::array,我无法重现它。
  • @Xeo 所以,是的,我想相信这是一个 GCC 错误,但一般来说,我宁愿确定它不是来自我对语言的理解 :)

标签: c++ c++11 compile-time constexpr constant-expression


【解决方案1】:

我知道这里发生了什么:

 constexpr std::initializer_list<int> b = { a[0], a[1], a[2] };

const int&amp; 类型的a[0] 隐式转换为const int 类型的临时值。 然后 g++ 将其转换为 const int* 以传递给 initializer_list 私有构造函数。 在最后一步中,它获取临时地址,因此它不是常量表达式。

问题在于隐式转换为 const int。示例:

constexpr int v = 1;
const int& r = v; // ok
constexpr int& r1 = v; // error: invalid initialization of reference of
                       // type ‘int&’ from expression of type ‘const int’

clang 中也有同样的行为。

我认为这种转换是合法的,没有什么相反的说法。

关于const int&amp;const int的转换,[expr]第5段:

如果一个表达式最初的类型是“对 T 的引用”,那么该类型是 在任何进一步分析之前调整到 T。表达式指定 引用所指的对象或函数,表达式为 一个左值或一个 xvalue,取决于表达式。

a[0] 表达式的结果在这种情况下是 const int 类型的临时 xvalue。

关于 constexpr 初始化程序中的隐式转换,[dcl.constexpr] 第 9 段:

... 用于转换初始化程序的每个隐式转换 用于初始化的表达式和每个构造函数调用 应该是常量表达式中允许的那些之一。

关于临时取地址,[expr.const]第2段:

...使用参数调用 constexpr 函数,当 被函数调用替换,不要 产生一个常量表达式; [ 例子:

constexpr const int* addr(const int& ir) { return &ir; } // OK
static const int x = 5;
constexpr const int* xp = addr(x); // OK: (const int*)&(const int&)x is an
                                   // address contant expression
constexpr const int* tp = addr(5); // error, initializer for constexpr variable
                                   // not a constant expression;
                                   // (const int*)&(const int&)5 is not a
                                   // constant expression because it takes
                                   // the address of a temporary

—结束示例]

【讨论】:

  • 私有 ctor 不会使用您传递的元素调用,而是使用指向从您传递的元素初始化的 array 的指针以及大小或指针调用到最后。
  • 我实际上将a bug report 提交给了GCC。其中一个人设法让 g++ 拒绝另一个不涉及任何参考的示例。
  • 我主要指向const int&amp;const int 的转换,这会导致这种行为并且似乎是合法的。第一个示例显示了原始转换。关于ctor的部分只是解释错误信息。
【解决方案2】:

你的例子都是错误的。

tl/dr: 初始化器是非常量的,因为每次计算函数时它都会引用不同的临时值。

声明:

constexpr std::initializer_list<int> b = { a0, a1, a2 };

创建一个const int [3] (C++11 [dcl.init.list]p5) 类型的临时数组,然后将std::initializer_list&lt;int&gt; 对象绑定到该临时数组,就像通过绑定引用一样到它(C++11 [dcl.init.list]p6)。

现在,通过 C++11 [expr.const]p4

对于数组或类类型的字面常量表达式,每个子对象 [...] 应已由常量表达式初始化。 [...] 地址常量表达式 [...] 计算为具有静态存储持续时间的对象的地址。

由于b具有自动存储时长,当std::initializer_list&lt;int&gt;对象绑定到const int [3]临时时,临时也被赋予自动存储时长,所以b的初始化是 一个常量表达式,因为它引用了一个没有静态存储持续时间的对象的地址。所以b 的声明格式不正确。

为什么 GCC 接受一些 constexpr std::initializer_list 对象

在初始化程序非常简单的情况下,GCC(和 Clang)将数组提升到全局存储,而不是每次都创建一个新的临时数组。然而,在 GCC 中,这种实现技术会泄露到语言语义——GCC 将数组视为具有静态存储持续时间,并接受初始化(作为对 C++11 规则的意外或故意扩展)。

解决方法(仅限 Clang)

您可以通过为 std::initializer_list&lt;int&gt; 对象提供静态存储持续时间来使您的示例有效:

static constexpr std::initializer_list<int> b = { a0, a1, a2 };

这反过来又为数组临时提供了静态存储持续时间,这使得初始化成为一个常量表达式。

使用 Clang 和 libc++(将 constexpr 添加到 libc++ 的 &lt;array&gt;&lt;initializer_list&gt; 的适当位置),添加 static 的调整足以让您的示例被接受。

使用 GCC,示例仍然被拒绝,诊断如下:

<stdin>:21:61: error: ‘const std::initializer_list<int>{((const int*)(& _ZGRZ4mainE1c0)), 1u}’ is not a constant expression

这里,_ZGRZ4mainE1c0 是生命周期延长的临时数组(具有静态存储持续时间)的错位名称,我们可以看到 GCC 隐式调用(私有)initializer_list&lt;int&gt;(const int*, size_t) 构造函数。我不确定为什么 GCC 仍然拒绝这个。

【讨论】:

    猜你喜欢
    • 2019-12-07
    • 1970-01-01
    • 1970-01-01
    • 2013-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多