15

这是对该主题的某种跟进,并涉及其中的一小部分。与上一个主题一样,让我们​​考虑一下我们的编译器具有constexpr用于std::initializer_list和的函数std::array。现在,让我们直奔主题。

这有效

#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 将无法编译第一个片段,因为它没有实现constexpr为 C++14 计划的库添加。我使用了 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;
}
4

2 回答 2

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++<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 仍然拒绝这个。

于 2013-07-14T00:24:56.170 回答
2

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

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

a[0]of typeconst int&隐式转换为临时 type 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。表达式指定引用表示的对象或函数,表达式是左值或 x 值,具体取决于表达式。

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

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

—结束示例]

于 2013-04-18T16:54:40.053 回答