关于常量表达式的困惑

时间:2013-04-17 10:29:41

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

这是this topic的某种后续行动,并涉及其中的一小部分。与前一主题一样,我们假设我们的编译器具有constexprstd::initializer_list的{​​{1}}函数。现在,让我们直截了当。

This works

std::array

This does not

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

它崩溃了这个错误:

#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 和常量表达式的论文,但这种行为对我来说仍然没有任何意义。为什么第一个例子被认为是有效的常量表达而不是第二个?我欢迎任何解释,以便我可以在此之后安息。

注意:我会马上准确的,Clang将无法编译第一个代码段,因为它没有实现为C ++计划的constexpr库添加项14 。我使用了GCC 4.7。

编辑:好的,这里有一个很好的例子来展示被拒绝的内容和不被拒绝的内容:

constexpr

2 个答案:

答案 0 :(得分:2)

我弄清楚这里发生了什么:

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

const int&隐式转换为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&const int次转化,[expr]第5段:

  

如果表达式最初具有“对T的引用”类型,则类型为   在进行任何进一步分析之前调整到T.表达式指定   由引用表示的对象或函数,表达式为   lvalue或xvalue,取决于表达式。

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

关于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
     

- 结束示例]

答案 1 :(得分:2)

您的示例都是格式不正确。

tl / dr:初始值设定项是非常量的,因为每次评估函数时它都会引用不同的临时值。

宣言:

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

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

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

  

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

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

为什么GCC会接受部分constexpr std::initializer_list个对象

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

解决方法(仅限Clang)

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

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

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

使用Clang和libc ++(将constexpr添加到libc ++&#39; s <array><initializer_list>的相应位置),这个添加static的调整足以让你的例子被接受。

使用GCC,这些示例仍被拒绝,其诊断如下:

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

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