2013-04-17 50 views
13

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

注:我会精确它的时候了,锵将无法编译第一个片段,因为它不执行该计划用于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; 
} 
+0

“GCC有bug”? :)(不是说它有一个,只是一种可能性。)实际上,你应该可以通过编写你自己的类似物来测试这个,而不需要增加constexpr。另外,'constexpr std :: array b = {{a [0],a [1],a [2]}};'? – Xeo

+1

也许[this](http://ideone.com/56iP0Y)有助于缩小问题的范围 –

+0

@Xeo无论我如何处理数组,似乎都可以正常工作(包括您的示例,而Andy只使用'std :: arrays' 'std :: initializer_list')。看起来问题只发生在编译时的'std :: initializer_list'。没有'constexpr'或'std :: array',我没有设法重现它。 – Morwenn

回答

1

我想通了,这是怎么回事就在这里:

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’ 

相同的行为是在铛。

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

关于const int&const int转换,[EXPR]段落5:

If an expression initially has the type “reference to T” , the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.

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

关于在constexpr初始化的隐式转换,[dcl.constexpr]段落9:

... Each implicit conversion used in converting the initializer expressions and each constructor call used for the initialization shall be one of those allowed in a constant expression.

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

...an invocation of a constexpr function with arguments that, when substituted by function invocation substitution, do not produce a constant expression; [ Example:

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 

— end example ]

+0

私人ctor不会与您通过的元素,它被一个指向* array *的指针所调用,该指针由您传递的元素初始化,并指向一个大小或指向末尾的指针。 – Xeo

+0

我实际上向GCC提交了[错误报告](http://gcc.gnu.org/bugzilla/show_bug.cgi?id=56991)。其中一个人设法让g ++拒绝另一个不涉及任何参考的例子。 – Morwenn

+0

我主要指向'const int&'const const'转换,这会导致这样的行为,并且似乎是合法的。第一个例子显示原始转换。关于ctor的部分是仅解释错误消息。 –

1

你的例子都是不合格的。

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

For a literal constant expression of array or class type, each subobject [...] shall have been initialized by a constant expression. [...] An address constant expression [...] evaluates to the address of an object with static storage duration.

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

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

在的情况下初始化适宜琐碎,GCC的(和锵)促进阵列全球存储,而不是围绕创建一个新的临时各一次。然而,在GCC中,这种实现技术渗透到语言语义 - GCC将数组视为具有静态存储持续时间,并接受初始化(作为C++ 11规则的意外扩展或故意扩展)。

解决方法(只锵)

你可以让你的例子有效通过给std::initializer_list<int>对象静态存储时间:

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

这反过来又使静态存储到阵列暂时的,这使初始化成为一个常量表达式。

使用锵和的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隐含地调用(私有)构造函数。我不确定为什么海湾合作委员会仍然拒绝这一点。