Подтвердить что ты не робот

С++ Почему я могу инициализировать статический const char, но не статический const double в определении класса?

Вот две строки кода:

static const double RYDBERG_CONST_EV = 13.6056953;
static const char CHAR_H_EDGE = '-';

Вторая строка компилируется без ошибок, первая строка не компилируется. (Ошибка: 'constexpr' needed for in-class initialization of static data member...)

Решение, по-видимому, должно добавить ключевое слово constexpr перед типом. Это необходимо, потому что double не является "интегральным типом". Но почему поведение отличается от целочисленного и с плавающей запятой?

4b9b3361

Ответ 1

Я не верю, что для этого есть веская причина, за исключением того, что она исторически выросла.

Исключение для интегральных типов было желательно в pre-С++ 11, потому что люди хотели использовать их в качестве размеров массива. Это согласуется с другим исключением для интегральных муравьев const, которые рассматриваются как постоянные выражения. Исключение, которое не существует для типов с плавающей запятой.

const int    ni = 10;
const float  nf = 10.0f;
int numbers1[(unsigned) ni];  // perfectly fine in all versions of C++
int numbers2[(unsigned) nf];  // error in all versions of C++

Когда С++ 11 представил constexpr, он мог делать все, что мог бы сделать особый корпус для const интегральных типов и многое другое. И он работает одинаково для любого литерала. Поэтому, учитывая превосходный инструмент, не было необходимости расширять существующие правила для интегральных типов до плавающей запятой.

Сегодня специальная оболочка интегральных типов в основном оставлена ​​с более ранних более темных дней. Он не может быть удален из языка, потому что это приведет к поломке существующего кода, который опирается на этот особый корпус, но будет еще мало шансов усложнить язык еще больше, добавив больше исключений, которые сегодня были бы полностью ненужными благодаря constexpr. От людей следует ожидать перехода на constexpr и больше не беспокоиться о старой трещине. Я считаю, что это было очень разумное решение, но вы, несомненно, могли бы утверждать, что нужно было принять другое решение.

Добавление

Как TC прокомментировал, был отчет о дефекте (non) - об этой проблеме, когда комитет подтвердил, что поведение не будет изменено, и люди должны начать использовать constexpr.

1826. const плавающая точка в постоянных выражениях

Раздел: 5.20 [expr.const] Статус: NAD Отправитель: Ville Voutilainen Дата: 2014-01-04

A const целое число, инициализированное константой, может использоваться в константных выражениях, но переменная с плавающей запятой const, инициализированная константой, не может. Это было преднамеренно, чтобы быть совместимым с С++ 03, одновременно поощряя последовательное использование constexpr. Однако некоторые люди считают это различие удивительным.

Было также отмечено, что использование переменных const с плавающей запятой в качестве постоянных выражений будет изменением ABI, поскольку оно повлияет на захват лямбда.

Одной из возможностей может быть отказ от использования интегральных переменных const в постоянных выражениях.

Дополнительная заметка, апрель 2015 г.

EWG запросил CWG, чтобы разрешить использование переменных const с плавающей запятой в постоянных выражениях.

Обоснование (май 2015 г.):

CWG считал, что текущие правила не должны меняться и что программисты, желающие принимать значения с плавающей запятой для участия в постоянных выражениях, должны использовать constexpr вместо const.

Ответ 2

Для формулировки, § [class.static.data]/3 говорит:

  1. Если постоянный элемент статистических данных, не являющийся volatile, имеет тип интеграла или перечисления, его объявление в классе определение может указывать вставку-выравниватель, в которой каждое предложение-инициализатор, являющееся выражением-присваивания, является постоянным выражением (5.20). Статический член данных типа literal может быть объявлен в определении класса с помощью спецификатора constexpr; если это так, в его декларации указывается логический или равный-инициализатор, в котором каждое предложение-инициализатор, являющееся выражением-присваивания, является постоянным выражением.

(emph mine). Обратите внимание, что в случае интегральных типов элемент статических данных может не иметь инициализации (вы всегда можете вывести его за пределы определения класса). Плюс, единственный способ получить активацию внутри определения класса - с помощью constexpr.

Обоснование (IMHO) о возможности инициализации интегральных типов (также в С++ 98) в определении класса состоит в том, чтобы включить очень простые шаблоны, подобные этому:

class Foo {
    static const size_t arrayLen = 42;
    int foo[arrayLen];
};

который без инициализации внутри тела становится невозможным.

Ответ 3

Известно, что:

  • static const Элементы интегрального типа могут быть инициализированы в определении класса.

  • static constexpr члены могут быть инициализированы в определении класса

double не является интегральным типом и должен быть помечен как constexpr.

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

Маркировка объекта static говорит о том, что он может быть известен всем наблюдателям, а его const говорит о том, что значение не изменяется. Компилятор может сгенерировать значение (например, 314) и поместить его в раздел только для чтения, потому что диапазон определен в стандарте.

С другой стороны, double не находится в стандарте и не может иметь свою ранговую проверку и значение, хранящееся во время компиляции, в определениях классов. Вполне возможно, что у них есть разные объектные файлы с объектами, имеющими другое значение для этого static const double, тем самым нарушая ODR.

Вот простой пример:

struct foo
{
static char const a = 1/11; // everyone gets 0 at compile-time
};

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

struct foo
{
static double const y=1.0/11.0; // the true value is 0.090909... 
};

кажется проверяемым, но представление в double на одной машине будет 0.09091 в другом 0.090909091.

Использование constexpr позволяет сказать компилятору, что вход, необходимый для его проверки, доступен во время компиляции. Тем не менее, фактическая оценка может произойти во время выполнения.

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

Вопрос - типичный пример XY-problem. Вместо того, чтобы спрашивать: "Почему я должен отмечать что-либо с помощью constexpr?" задана головоломка char -vs- float. Теперь возникает вопрос: "Почему мы должны использовать constexpr для нецелых типов?" И здесь вы можете найти свой ответ.