Search code examples
c++language-lawyerc++20

Copy-initialization of array type


Considering the example below, where t is declared as int[1]; is the initialization of a in t a = t{}; legal according to the C++20 standard? Should the behavior of initialization t a = t{}; in any way be affected by marking a as constexpr? What does the C++20 standard say about this? And what about the initialization of constexpr auto a = t{};? Why does MSVC accept it?

static_assert([]{
    using t = int[1];
    constexpr t a{};        // all ok
    constexpr t a = {};     // all ok
    constexpr t a = t{};    // clang nope, gcc ok, msvc ok
    t a = t{};              // clang nope, gcc ok, msvc nope
    constexpr auto a = t{}; // clang nope, gcc nope, msvc ok
    return true;
}());

Live example


The error message from Clang:

<source>:4:7: error: array initializer must be an initializer list
    4 |     t a = t{};
      |       ^

The error message from GCC:

<source>:4:24: error: taking address of temporary array
    4 |     constexpr auto a = t{};
      |                        ^~~

The error message MSVC:

<source>(4): error C2075: 'a': initialization requires a brace-enclosed
initializer list

Solution

  • In C++17 constexpr t a = t{}; and t a = t{}; are both ill-formed, because initialization of a falls through until [dcl.init]/17.5 (of draft N4659), which effectively demands that arrays, with an exception for initialization of character arrays by string literals, can only be initialized by an initializer of the form {/*...*/} or = {/*...*/} or (), where /*...*/ is an optional comma-separated list of initializers. None of these match t{}.

    In C++20 aggregate initialization from parentheses was added, so that initializing arrays with an initializer of the form (/*...*/) became allowed. So the fall-through case 17.5 in the initialization rules was replaced with rules for parenthesized aggregate initialization (see [dcl.init.general]/16.5 in draft N4868). Unfortunately the new wording assumes that if the bullet is reached, the array initializer must have the form (/*...*/), ignoring the case in your question where t{} doesn't match any form.

    So the C++20 standard lacks a specification for these cases.

    CWG issue 2824 has already been reported, although it is concerned with the special case where the initializer is a string literal, not a cast expression, and the array not a character array. Regardless, the resolution which is marked as "tentatively ready" and approved by CWG would make all of the remaining initializer forms ill-formed again, as in C++17.

    So, once this is a proper defect report and implemented by the compiler vendors, both constexpr t a = t{}; and t a = t{}; should be ill-formed.


    constexpr auto a = t{}; is ill-formed, because the initialization is not a constant expression. You are trying to store a pointer into the temporary array materialized from t{}. This is not a permitted result of a constant expression per [expr.const]/11.2.

    Without constexpr it should be fine, but the pointer is immediately dangling after the initialization, because the temporary array is destroyed again. However, the initialization itself should be permitted, because [conv.array] specifies that the array-to-pointer conversion also applies to array rvalues.