无法从成员变量中的初始化字符串推导出数组大小的原因是什么?

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

考虑代码:

struct Foo
{
    const char str[] = "test";
};

int main()
{
    Foo foo;
}

它无法用 g++ 和 clang++ 编译,本质上是吐出来的

error: array bound cannot be deduced from an in-class initializer

我知道标准可能是这么说的,但是有什么特别好的理由吗?由于我们有一个字符串文字,因此编译器似乎应该能够毫无问题地推断出大小,类似于您简单地声明一个 out-of-class const C-like null terminated 的情况字符串。

原因是您始终可以在构造函数中覆盖 in-class 初始化列表。所以我想最后可能会很混乱。

struct Foo
{
   Foo() {} // str = "test[=10=]";

   // 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";
};

请注意,将 const char 替换为 static constexpr char 效果非常好,而且可能这正是您想要的。

如果允许编译器支持您描述的内容,并且 str 的大小被推断为 5

Foo foo = {{"This is not a test"}};

会导致未定义的行为。

正如评论中提到的和@sbabbi 的回答,答案在于细节

12.6.2 初始化基和成员[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 初始化基和成员[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 ]

因此,如果存在非删除构造函数,则忽略大括号或等于初始化器,并以构造函数成员内初始化为准。因此,对于省略了大小的数组成员,表达式变得不正确。 §12.6.2,第 9 项,在我们指定如果 mem 初始化由构造函数执行时省略 r 值初始化表达式的地方更明确。

另外,google小组讨论Yet another inconsitent behavior in C++,进一步阐述,使其更加清晰。它扩展了这个想法,解释了 brace-or-equal-initializer 是在成员的成员内初始化不存在的情况下的成员内初始化的一种美化方式。举个例子

struct Foo {
    int i[5] ={1,2,3,4,5};
    int j;
    Foo(): j(0) {};
}

相当于

struct Foo {
    int i[5];
    int j;
    Foo(): j(0), i{1,2,3,4,5} {};
}

但现在我们看到如果省略数组大小,表达式将是错误的。

但话说回来,编译器本可以支持成员未通过成员内构造函数初始化初始化的情况的功能,但目前为了统一起见,与许多其他标准一样,标准不支持此功能功能。