Почему не допускаются «встроенные» статические константы, кроме целых? - PullRequest
4 голосов
/ 15 декабря 2009

Возможный дубликат
Почему я не могу иметь в классе нецелый статический член const?

struct Example
{
    static const int One = 1000; // Legal
    static const short Two = 2000; // Illegal
    static const float Three = 2000.0f; // Illegal
    static const double Four = 3000.0; // Illegal
    static const string Five = "Hello"; // Illegal
};

Есть ли причина, по которой № 2, № 3, № 4 и № 5 являются незаконными?

Мне кажется, я знаю причину # 5: компилятору нужен «настоящий» строковый объект (поскольку он не является встроенным типом) и он не может бездумно заменить Five на "Hello", как если бы это был #define Five "Hello". Но если это так, не может ли компилятор оставить подсказку в файлах .obj и сказать компоновщику, чтобы он автоматически где-то создал один экземпляр string Five?

Для № 3 и № 4 и особенно № 2 (смеется!) ... Я действительно не вижу никакой возможной причины! Float и double являются встроенными типами, как и int! А short - это просто (возможно) более короткое целое число.


EDIT : я использую Visual Studio 2008 для его компиляции. Я думал, что все компиляторы ведут себя одинаково в этом случае, но, очевидно, g ++ компилирует это нормально (кроме # 5). VS дает ошибки для этого фрагмента:

    error C2864: 'Example::Two' : only static const integral data members can be initialized within a class
    error C2864: 'Example::Three' : only static const integral data members can be initialized within a class
    error C2864: 'Example::Four' : only static const integral data members can be initialized within a class
    error C2864: 'Example::Five' : only static const integral data members can be initialized within a class

Ответы [ 8 ]

7 голосов
/ 15 декабря 2009

Int и short допустимы, и если ваш компилятор не разрешает их, тогда ваш компилятор перебор:

9.4.2 / 4: ... Если элемент статических данных имеет постоянное или постоянное значение тип перечисления, его объявление в определение класса может указывать инициализатор константы , который должен быть целым выражением константы.

Я полагаю, что причина того, что числа с плавающей точкой и числа с двойными числами не рассматриваются специально как константы в стандарте C ++, так же как и целочисленные типы, заключается в том, что стандарт C ++ настороженно относится к тому, что арифметические операции над числами с плавающей точкой и double могут быть тонко отличается на компиляторе, чем на машине, которая выполняет код. Чтобы компилятор вычислял константное выражение типа (a + b), ему нужно получить тот же ответ, который получит среда выполнения.

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

Как упоминает Брайан, C ++ 0x собирается решить эту проблему с помощью constexpr. Если я прав насчет первоначальной мотивации, то, вероятно, 10 лет было достаточно для преодоления трудностей в определении этого материала.

4 голосов
/ 15 декабря 2009

Оба Example::One и Example::Two должны компилироваться для вас, и они действительно компилируются для меня в той же среде, которую вы указали (VS 2008).

Я не верю Example::Three, и Example::Four должен компилироваться вообще в стандартном C ++, но я думаю, что есть расширение gcc, которое позволяет это. Example::Five не должен компилироваться.

Вы можете инициализировать их так же после объявления структуры, обычно в вашем исходном файле:

const float Example::Three = 2000.0f;
const double Example::Four = 3000.0;
const string Example::Five = "Hello";

Это самый переносимый способ сделать это, и я бы рекомендовал это делать, даже если ваш компилятор позволяет вам определять Example::Three и Example::Four в вашем объявлении.

Другой вариант - просто вернуть значение из статической функции того же типа.

struct Example
{
    //...
    static double Four() { return  = 3000.0; }
    //...
};

В этом ответе также обсуждается возможная причина.
В этом ответе обсуждается, как грядущий стандарт C ++ поможет с помощью constexpr

1 голос
/ 15 декабря 2009

# 1 и 2 соответствуют стандарту. К сожалению, некоторые компиляторы просто не соответствуют. Вот почему, например, разработчикам Boost пришлось вводить надоедливые макросы типа BOOST_STATIC_CONSTANT для создания переносимых библиотек. Если вы не хотите определять константу в файле .cpp, переносимый обходной путь должен использовать enum. Хотя, очевидно, в этом случае у вас нет никаких гарантий относительно типа, и вы не можете использовать float.

1 голос
/ 15 декабря 2009

В C ++ 98 только статические константные члены целочисленные типы могут быть инициализированы в классе, и инициализатор должен быть постоянным выражением. Эти ограничения гарантируют, что мы можем сделать инициализация во время компиляции.

См. Инициализаторы членов класса .

§9.4.2 Элементы статических данных

Если статический член данных имеет константный интеграл или константный тип перечисления, его объявление в определении класса может задайте инициализатор константы, который должен быть выражением интегральной константы (5.19). В этом случае член может появиться в интегральных константных выражениях. Член по-прежнему должен быть определен в области пространства имен, если он используется в программе и определение области имен не должно содержать инициализатор.

0 голосов
/ 16 декабря 2009

Как уже отмечали другие, ваш компилятор в некоторых случаях не работает. Но я никогда не понимал причину, почему это не разрешено для типов с плавающей точкой, кроме «Стандарт говорит так». Похоже, нет веских технических причин.

0 голосов
/ 15 декабря 2009

Как выяснили другие, стандарт C ++ запрещает инициализировать статический член const значением с плавающей запятой.

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

Эта возможность существует в реальном оборудовании. Например, Intel x86 имеет пару битов в регистре управления с плавающей запятой, которые контролируют точность вычислений с плавающей запятой. По умолчанию вычисления выполняются на 80-битном длинном двойном типе и округляются только до 64-битного двойного или 32-битного числа с плавающей запятой по запросу. Эти биты в регистре могут быть изменены во время выполнения, поэтому (например) «1,23» в одном месте может инициализировать переменную одним значением, в то время как «1,23» в другой части программы (после корректировки точности) может привести к в (немного) другом значении.

По крайней мере, насколько я знаю, это остается теоретической возможностью, по крайней мере, на большинстве типичных машин. Хотя аппаратное обеспечение Intel допускает динамическую настройку точности FP, я не знаю ни одного компилятора (даже Intel), который бы пытался учесть такую ​​настройку при переводе литералов FP (хотя компилятор Intel по крайней мере поддерживает 80-битную длину двойной тип).

0 голосов
/ 15 декабря 2009

Что касается инициализаторов с плавающей запятой, то в спецификации C ++ 98 есть (5.19):

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

0 голосов
/ 15 декабря 2009

Под VS2008 я получаю следующую ошибку:

1>.\Weapon Identification SystemDlg.cpp(223) : error C2864: 'Example::Three' : only static const integral data members can be initialized within a class
1>.\Weapon Identification SystemDlg.cpp(224) : error C2864: 'Example::Four' : only static const integral data members can be initialized within a class
1>.\Weapon Identification SystemDlg.cpp(225) : error C2864: 'Example::Five' : only static const integral data members can be initialized within a class

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

...