Confusion about constant expressions

前端 未结 2 754
故里飘歌
故里飘歌 2021-02-13 04:07

This is some kind of follow-up for this topic and deals about a little part of it. As with the previous topic, let\'s consider that our compiler has constexpr funct

相关标签:
2条回答
  • 2021-02-13 04:39

    Your examples are all ill-formed.

    tl/dr: The initializer is non-constant because it refers to a different temporary each time the function is evaluated.

    The declaration:

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

    creates a temporary array of type const int [3] (C++11 [dcl.init.list]p5), then binds the std::initializer_list<int> object to that temporary as if by binding a reference to it (C++11 [dcl.init.list]p6).

    Now, by 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.

    Since b has automatic storage duration, when the std::initializer_list<int> object binds to the const int [3] temporary, the temporary is also given automatic storage duration, so the initialization of b is not a constant expression because it refers to the address of an object that does not have static storage duration. So the declaration of b is ill-formed.

    Why GCC accepts some of the constexpr std::initializer_list objects

    In cases where the initializer is suitably trivial, GCC (and Clang) promote the array to global storage rather than creating a new temporary each time around. However, in GCC, this implementation technique leaks through to the language semantics -- GCC treats the array as having static storage duration, and accepts the initialization (as either an accidental or deliberate extension to the C++11 rules).

    Workaround (Clang only)

    You can make your examples valid by giving the std::initializer_list<int> objects static storage duration:

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

    This in turn gives static storage duration to the array temporary, which makes the initialization be a constant expression.

    Using Clang and libc++ (with constexpr added to libc++'s <array> and <initializer_list> in the appropriate places), this tweak of adding static is sufficient for your examples to be accepted.

    Using GCC, the examples are still rejected, with diagnostics such as:

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

    Here, _ZGRZ4mainE1c0 is the mangled name of the lifetime-extended array temporary (with static storage duration), and we can see that GCC is implicitly calling the (private) initializer_list<int>(const int*, size_t) constructor. I am not sure why GCC is still rejecting this.

    0 讨论(0)
  • 2021-02-13 04:44

    I figured out what is going on here:

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

    a[0] of type const int& implicitly converts to a temporary of type const int. Then g++ converts it to const int* to pass into the initializer_list private constructor. In the last step it takes address of a temporary, so it is not a constant expression.

    The problem is in implicit conversion to const int. Example:

    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’
    

    The same behavior is in clang.

    I think this conversion is legal, nothing says the opposite.

    About const int& to const int conversion, [expr] paragraph 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.

    The result of a[0] expression is the temporary xvalue of type const int in that case.

    About implicit conversions in constexpr initializer, [dcl.constexpr] paragraph 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.

    About taking address of temporary, [expr.const] paragraph 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 讨论(0)
提交回复
热议问题