Which of these new expressions with char arrays are well-formed?

Viewed 260

For the following program:

int main() 
{
    new char[4] {"text"};  // #1
    new char[5] {"text"};  // #2
    new char[] {"text"};   // #3
}

clang gives an error for #1 which says:

error: initializer-string for char array is too long

and accepts #2 and #3.

gcc gives the following error for all statements:

error: invalid conversion from 'const char*' to 'char' [-fpermissive]

and in addition for #3 it gives the error:

error: expected primary-expression before ']' token

So what does the language say about whether this code is well-formed?

I want to know the current rules, but I'd also be interested to know if this has this changed in previous versions of the language.

2 Answers

OK, this is pretty simple to trace. The presence of {} means that list initialization is being performed, so we get to visit our favorite part of the spec: [dcl.init.list]/3.

The object being initialized in case 1 is a char[4]. The braced-init-list is not a designated initializer, so 3.1 is ignored. char[4] is not a class, so 3.2 is ignored. That brings us to 3.3:

Otherwise, if T is a character array and the initializer list has a single element that is an appropriately-typed string-literal ([dcl.init.string]), initialization is performed as described in that subclause.

Well, char[4] is definitely a character array, and the initializer list definitely contains a single element, and that element does in fact match the type of the character array. So off to [dcl.init.string] we go.

That tells us (after a fashion):

Successive characters of the value of the string-literal initialize the elements of the array.

But the next paragraph warns:

There shall not be more initializers than there are array elements.

Well, that makes #1 ill-formed.

So, we redo the process for char[5]. And that doesn't trigger, since 5 is sufficiently large.

Lastly, we come to char[]. Which is no different from using a number, as far as initialization is concerned. char[] is an array of characters, so it follows the above rules. C++17 would choke on using char[] in a new expression, but C++20 is fine with it.

If the type-id or new-type-id denotes an array type of unknown bound ([dcl.array]), the new-initializer shall not be omitted; the allocated object is an array with n elements, where n is determined from the number of initial elements supplied in the new-initializer ([dcl.init.aggr], [dcl.init.string]).

Which means that #2 and #3 are supposed to be legal. So GCC is wrong to make them ill-formed. And it makes #1 ill-formed for the wrong reason.

Clang is correct in that #1 is ill-formed and #2 is okay.

As Ted Lyngmo noted in a comment, #3 was invalid by the C++ grammar rules, until paper P1009R2 made a change to allow it. A new-expression simply did not allow the possiblity of empty [] in the type, left over from when there was no syntax to initialize the array created by a new-expression, and therefore no way for a compiler to determine the actual size. The paper's changes are accepted in C++20 (but compiler writers will sometimes choose to support "fixes" retroactively in previous -std= modes).

For the difference between #1 and #2, the initialization of the array object is specified in [expr.new] to follow the direct-initialization rules of [dcl.init]. The general rules for initialization in [dcl.init] early on say if the initializer is a braced-init_list, it is list-initialization. The rules for this in [dcl.init.list] go like:

List-initialization of an object or reference of type T is defined as follows:

  • [C++20 only:] If the braced-init-list contains a designated-initializer-list, ...

  • If T is an aggregate class and...

  • Otherwise, if T is a character array and the initializer list has a single element that is an appropriately-typed string-literal ([dcl.init.string]), initialization is performed as described in that subclause.

  • ...

And so [dcl.init.string] (C++17, latest) gives the actual initialization rules which apply to this code:

An array of {C++17: narrow character type}{C++20: ordinary character type ([basic.fundamental])}, char8_­t array, char16_­t array, char32_­t array, or wchar_­t array can be initialized by {C++17: a narrow}{C++20: an ordinary} string literal, UTF-8 string literal, UTF-16 string literal, UTF-32 string literal, or wide string literal, respectively, or by an appropriately-typed string-literal enclosed in braces ([lex.string]). Successive characters of the value of the string-literal initialize the elements of the array.

There shall not be more initializers than there are array elements. [ Example:

char cv[4] = "asdf";            // error

is ill-formed since there is no space for the implied trailing '\0'. — end example ]

If there are fewer initializers than there are array elements, each element not explicitly initialized shall be zero-initialized ([dcl.init]).

Just like the plain variable definition, when the character array type of a new-expression has a specified bound, it must be large enough for all the characters of a string literal initializing it, including the trailing null character.

(This is an old difference between C and C++: C does allow char cv[4] = "asdf"; and ignores the null character.)

Related