What is the reason for not being able to deduce array size from initializer-string in member variable?

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.


Solution 1:

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.

Solution 2:

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]

  1. 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]

  1. 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.

Solution 3:

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.