В чем причина невозможности вывода размера массива из строки инициализации в переменной-члене?
Рассмотрим код:
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-like, завершающую нуль.
Ответы
Ответ 1
Причина в том, что у вас всегда есть возможность переопределить список инициализаторов класса в конструкторе. Поэтому я предполагаю, что в конце концов это может быть очень запутанным.
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
работает отлично, и, вероятно, это то, что вы хотите в любом случае
Ответ 2
Если компилятору было разрешено поддерживать то, что вы описали, а размер str
был выведен на 5
,
Foo foo = {{"This is not a test"}};
приведет к поведению undefined.
Ответ 3
Как упоминалось в комментариях, и как ответил @sbabbi, ответ кроется в деталях
12.6.2 Инициализация баз и членов [class.base.init]
-
В конструкторе без делегирования, если данный нестатический член данных или базовый класс не обозначается идентификатором mem-initializer-id (включая если нет mem-initializer-list, потому что конструктор не имеет ctor-инициализатора), и сущность не является виртуальным базовым классом абстрактный класс (10.4), то
- если объект является нестатистическим элементом данных, у которого есть инициализатор скобок или равный-инициализатор, объект инициализируется, как указано в 8,5;
- в противном случае, если объект является анонимным объединением или вариантом (9.5), инициализация не выполняется;
- в противном случае объект инициализируется по умолчанию
12.6.2 Инициализация баз и членов [class.base.init]
-
Если данный нестатический член данных имеет как скользящий или равный-инициализатор и mem-инициализатор, инициализация заданный mem-инициализатором, и нестатические данные Элемент-член-бит-или-равный-инициализатор игнорируется. [Пример: данный
struct A {
int i = /∗ some integer expression with side effects ∗/ ;
A(int arg) : i(arg) { }
// ...
};
конструктор A (int) просто инициализирует я значением arg, и побочные эффекты в - скобки или равные-инициализаторы не будут место. - конец примера]
Итак, если есть конструктор, не содержащий удаления, то проиграть с помощью элемента brace-or-equal-initializer игнорируется, и инициализация in-member конструктора превалирует. Таким образом, для элементов массива, для которых размер опущен, выражение становится плохо сформированным. § 12.6.2, пункт 9, делает его более явным, когда мы указали, что выражение инициализатора r-значения опущено, если конструктор выполняет инициализацию mem-инициализации.
Кроме того, google group dicussion Еще одно несоответствующее поведение на С++, далее уточняет и делает его более ясным. Это расширяет идею в объяснении того, что механизм скрепления или равный-инициализатор является прославленным способом инициализации внутри члена для случаев, когда инициализация члена в члене не существует. В качестве примера
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} {};
}
но теперь мы видим, что если размер массива был опущен, выражение будет плохо сформировано.
Но затем, говоря, что компилятор мог поддерживать эту функцию для случаев, когда член не инициализируется инициализацией конструктора внутри элемента, но в настоящее время для равномерности, стандарт, как и многие другие, не поддерживает эту функцию.