В чем причина невозможности определить размер массива из строки инициализатора в переменной-члене?

Рассмотрим код:

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

int main()
{
    Foo foo;
}

Он не компилируется как с g++, так и с clang++, по существу выплевывая

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

Я понимаю, что это то, что, вероятно, говорит стандарт, но есть ли какая-то особая веская причина? Поскольку у нас есть строковый литерал, кажется, что компилятор должен быть в состоянии определить размер без каких-либо проблем, подобно тому, как в случае, когда вы просто объявляете внеклассовую const C-подобную строку с завершающим нулем.


person vsoftco    schedule 12.04.2015    source источник
comment
Связанный: заголовок stackoverflow.com/questions/22509876/ и stackoverflow.com/questions/9656941 /   -  person aruisdante    schedule 12.04.2015
comment
потому что str также может быть инициализирован в списке-инициализации-членов конструктора Foo, отбрасывая инициализатор из инициализатора в классе   -  person Piotr Skotnicki    schedule 12.04.2015
comment
Обсуждение связанной проблемы: groups.google. com/a/isocpp.org/d/msg/std-discussion/lzcGWLDHxr4/   -  person Brian Bi    schedule 12.04.2015
comment
@aruisdante спасибо, вы можете отметить это как обман, я не смог найти обман самостоятельно. Хотя только первая ссылка, вероятно, является обманом, без принятого ответа.   -  person vsoftco    schedule 12.04.2015
comment
@ПиотрС. вы должны опубликовать ответ, как теперь это ясно!   -  person vsoftco    schedule 12.04.2015
comment
@vsoftco Вот почему я связал их как связанные, а не как дубликаты. Ни один из наборов вопросов и ответов не описывает проблему полностью, но оба они важны для понимания возможного ответа.   -  person aruisdante    schedule 12.04.2015


Ответы (3)


Причина в том, что у вас всегда есть возможность переопределить список инициализаторов класса в конструкторе. Так что я предполагаю, что, в конце концов, это может быть очень запутанным.

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

Обратите внимание, что замена const char на static constexpr char работает отлично, и, вероятно, это именно то, что вам нужно.

person sbabbi    schedule 12.04.2015
comment
Спасибо, имеет смысл. Есть ли способ сделать эту работу до С++ 11? Простое использование static const не работает. - person vsoftco; 12.04.2015
comment
@vsoftco да, это возможно в версиях до C++11 - person Piotr Skotnicki; 13.04.2015

Как упоминалось в комментариях и как ответил @sbabbi, ответ кроется в деталях.

12.6.2 Инициализация баз и членов [class.base.init]

  1. В конструкторе без делегирования, если данный нестатический член данных или базовый класс не обозначен идентификатором-инициализатора-mem (включая случай, когда нет списка-инициализатора-mem, потому что конструктор не имеет инициализатора ctor) и объект не является виртуальным базовым классом абстрактного класса (10.4), то

    • 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;
    • в противном случае, если объект является анонимным объединением или вариантным членом (9.5), инициализация не выполняется;
    • в противном случае объект инициализируется по умолчанию

12.6.2 Инициализация баз и членов [class.base.init]

  1. Если данный нестатический член данных имеет как инициализатор скобок или равенства, так и инициализатор памяти, выполняется инициализация, указанная инициализатором памяти, и инициализатор скобок или равенства нестатического члена данных игнорируется. [Пример: Дано

    struct A { 
        int i = /∗ some integer expression with side effects ∗/ ; 
        A(int arg) : i(arg) { } 
        // ... 
    };
    

конструктор A(int) просто инициализирует i значением arg, и побочные эффекты в инициализаторе скобок или равенства i не будут иметь места. — конец примера]

Таким образом, если имеется неудаляющий конструктор, инициализатор скобок или равенства игнорируется, и превалирует инициализация конструктора в члене. Таким образом, для элементов массива, для которых не указан размер, выражение становится неправильным. §12.6.2, пункт 9, делает это более явным, где мы указали, что выражение инициализатора r-значения опускается, если инициализация памяти выполняется конструктором.

Кроме того, обсуждение группы Google еще одно непоследовательное поведения в C++, дорабатывает и делает его более понятным. Это расширяет идею, объясняя, что инициализатор скобок или равенства — это прославленный способ инициализации в члене для случаев, когда инициализация внутри члена для члена не существует. В качестве примера

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} {};
}

но теперь мы видим, что если бы размер массива был опущен, выражение было бы некорректным.

Но, говоря это, компилятор мог бы поддерживать эту функцию для случаев, когда член не инициализируется инициализацией конструктора внутри члена, но в настоящее время ради единообразия стандарт, как и многие другие вещи, не поддерживает эту функцию.

person Abhijit    schedule 12.04.2015
comment
Интересно, что это предполагает, что поведение может быть разрешено, если каждый объявленный пользователем конструктор будет удален, а конструктор по умолчанию сгенерирован неявно. Возможно, стоит сделать предложение? - person Justin Time - Reinstate Monica; 06.10.2019

Если бы компилятору было разрешено поддерживать то, что вы описали, и размер str был выведен до 5,

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

приведет к неопределенному поведению.

person R Sahu    schedule 12.04.2015