Consider the code:
struct Foo
{
const char str[] = "test";
};
int main()
{
Foo foo;
}
It fails to compile with both g++ and clang++, spitting out essentially
error: array bound cannot be deduced from an in-class initializer
I understand that this is what the standard probably says, but is there any particular good reason why? Since we have a string literal it seems that the compiler should be able to deduce the size without any problem, similarly to the case when you simply declare an out-of-class const
C-like null terminated string.
The reason is that you always have the possibility to override an in-class initializer list in the constructor. So I guess that in the end, it could be very confusing.
struct Foo
{
Foo() {} // str = "test\0";
// Implementing this is easier if I can clearly see how big `str` is,
Foo() : str({'a','b', 'c', 'd'}) {} // str = "abcd0"
const char str[] = "test";
};
Notice that replacing const char
with static constexpr char
works perfectly, and probably it is what you want anyway.
If the compiler was allowed to support what you described, and the size of str
was deduced to 5
,
Foo foo = {{"This is not a test"}};
will lead to undefined behavior.
As mentioned in the comments and as answered by @sbabbi, the answer lies in the details
12.6.2 Initializing bases and members [class.base.init]
In a non-delegating constructor, if a given non-static data member or
base class is not designated by a mem-initializer-id (including the
case where there is no mem-initializer-list because the constructor
has no ctor-initializer) and the entity is not a virtual base class of
an abstract class (10.4), then
- if the entity is a non-static data member that has a brace-or-equal-initializer , the entity is initialized as specified in
8.5;
- otherwise, if the entity is an anonymous union or a variant member (9.5), no initialization is performed;
- otherwise, the entity is default-initialized
12.6.2 Initializing bases and members [class.base.init]
If a given non-static data member has both a
brace-or-equal-initializer and a mem-initializer, the initialization
specified by the mem-initializer is performed, and the non-static data
member’s brace-or-equal-initializer is ignored. [ Example: Given
struct A {
int i = /∗ some integer expression with side effects ∗/ ;
A(int arg) : i(arg) { }
// ...
};
the A(int) constructor will simply initialize i to the value of arg,
and the side effects in i’s brace-or equal-initializer will not take
place. — end example ]
So, if there is a non-deleting constructor, the brace-or-equal-initializer is ignored, and the constructor in-member initialization prevails. Thus, for array members for which the size is omitted, the expression becomes ill-formed. §12.6.2, item 9, makes it more explicit where we it specified that the r-value initializer expression is omitted if mem-initialization is performed by the constructor.
Also, the google group dicussion Yet another inconsitent behavior in C++, further elaborates and makes it more lucid. It extends the idea in explaining that brace-or-equal-initializer is a glorified way of an in-member initialization for cases where the in-member initialization for the member does not exist. As an example
struct Foo {
int i[5] ={1,2,3,4,5};
int j;
Foo(): j(0) {};
}
is equivalent to
struct Foo {
int i[5];
int j;
Foo(): j(0), i{1,2,3,4,5} {};
}
but now we see that if the array size was omitted, the expression would be ill-formed.
But then saying that, the compiler could have supported the feature for cases when the member is not initialized by in-member constructor initialization but currently for the sake of uniformity, the standard like many other things, does not support this feature.