对常量表达式的困惑

Confusion about constant expressions

本文关键字:表达式 常量      更新时间:2023-10-16

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

这有效:

#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

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

注意:我会立即精确地使用它,Clang 将无法编译第一个代码片段,因为它没有实现计划在 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;
}
我想

出了这里发生了什么:

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

类型 const int&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’

同样的行为也在叮当声中。

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

关于const int&const int转换,[expr]第5款:

如果表达式最初具有"引用 T"的类型,则类型为 在进行任何进一步分析之前调整为 T。表达式指定 引用表示的对象或函数,表达式为 左值或 x值,具体取决于表达式。

在这种情况下,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

— 结束示例 ]

你的例子都是格式错误的。

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++ 的 <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)构造函数。我不确定为什么海湾合作委员会仍然拒绝这一点。