25

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.

vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • Related: http://stackoverflow.com/questions/22509876/why-the-compiler-cant-set-size-of-char-in-a-struct-member-like-it-do and http://stackoverflow.com/questions/9656941/why-i-cant-initialize-non-const-static-member-or-static-array-in-class – aruisdante Apr 12 '15 at 18:52
  • 6
    because `str` can be also initialized in the member-initialization-list of Foo's constructor, discarding the initializer from the in-class initializer – Piotr Skotnicki Apr 12 '15 at 18:54
  • 1
    Discussion on a related issue: https://groups.google.com/a/isocpp.org/d/msg/std-discussion/lzcGWLDHxr4/5z4ZEmCCzLMJ – Brian Bi Apr 12 '15 at 18:54
  • @aruisdante thanks, you can mark it as a dupe, I wasn't able to find a dupe myself. Although only the first link is probably a dupe, with no accepted answer. – vsoftco Apr 12 '15 at 18:54
  • @PiotrS. you should post an answer as it now makes it clear! – vsoftco Apr 12 '15 at 18:56
  • @vsoftco That's why I linked them as related, rather than duplicates. Neither question/answer set fully describes the problem, but they're both important to understanding a possible answer. – aruisdante Apr 12 '15 at 18:59

3 Answers3

21

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.

sbabbi
  • 11,070
  • 2
  • 29
  • 57
6

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.

JDługosz
  • 5,592
  • 3
  • 24
  • 45
Abhijit
  • 62,056
  • 18
  • 131
  • 204
  • Interestingly, this suggests that the behaviour _could_ be allowed if every user-declared constructor is deleted, and the default constructor is implicitly generated. Perhaps worth a proposal? – Justin Time - Reinstate Monica Oct 06 '19 at 02:38
2

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.

R Sahu
  • 204,454
  • 14
  • 159
  • 270